@graphql-tools/executor-http 1.1.14 → 1.2.0-alpha-8e436b6c005eab0d1484933b57b778269906f0f6
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/CHANGELOG.md +29 -0
- package/dist/index.cjs +206 -166
- package/dist/index.d.cts +32 -7
- package/dist/index.d.ts +32 -7
- package/dist/index.js +207 -167
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @graphql-tools/executor-http
|
|
2
2
|
|
|
3
|
+
## 1.2.0-alpha-8e436b6c005eab0d1484933b57b778269906f0f6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#313](https://github.com/graphql-hive/gateway/pull/313) [`7530964`](https://github.com/graphql-hive/gateway/commit/753096403f92c5dbcdd17828acdd17a1aa53a51e) Thanks [@ardatan](https://github.com/ardatan)! - Automatic Persisted Queries support for upstream requests
|
|
8
|
+
|
|
9
|
+
For HTTP Executor;
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
buildHTTPExecutor({
|
|
13
|
+
// ...
|
|
14
|
+
apq: true,
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For Gateway Configuration;
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
export const gatewayConfig = defineConfig({
|
|
22
|
+
transportEntries: {
|
|
23
|
+
'*': {
|
|
24
|
+
options: {
|
|
25
|
+
apq: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
3
32
|
## 1.1.14
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -30,32 +30,21 @@ function collectAsyncIterableValues(asyncIterable) {
|
|
|
30
30
|
}
|
|
31
31
|
return iterate();
|
|
32
32
|
}
|
|
33
|
-
function createFormDataFromVariables({
|
|
34
|
-
query,
|
|
35
|
-
variables,
|
|
36
|
-
operationName,
|
|
37
|
-
extensions
|
|
38
|
-
}, {
|
|
33
|
+
function createFormDataFromVariables(body, {
|
|
39
34
|
File: FileCtor = fetch.File,
|
|
40
35
|
FormData: FormDataCtor = fetch.FormData
|
|
41
36
|
}) {
|
|
42
|
-
|
|
37
|
+
if (!body.variables) {
|
|
38
|
+
return JSON.stringify(body);
|
|
39
|
+
}
|
|
40
|
+
const vars = Object.assign({}, body.variables);
|
|
43
41
|
const { clone, files } = extractFiles.extractFiles(
|
|
44
42
|
vars,
|
|
45
43
|
"variables",
|
|
46
44
|
(v) => extractFiles.isExtractableFile(v) || v?.promise || utils.isAsyncIterable(v) || v?.then || typeof v?.arrayBuffer === "function"
|
|
47
45
|
);
|
|
48
46
|
if (files.size === 0) {
|
|
49
|
-
return JSON.stringify(
|
|
50
|
-
{
|
|
51
|
-
query,
|
|
52
|
-
variables,
|
|
53
|
-
operationName,
|
|
54
|
-
extensions
|
|
55
|
-
},
|
|
56
|
-
null,
|
|
57
|
-
2
|
|
58
|
-
);
|
|
47
|
+
return JSON.stringify(body);
|
|
59
48
|
}
|
|
60
49
|
const map = {};
|
|
61
50
|
const uploads = [];
|
|
@@ -69,10 +58,8 @@ function createFormDataFromVariables({
|
|
|
69
58
|
form.append(
|
|
70
59
|
"operations",
|
|
71
60
|
JSON.stringify({
|
|
72
|
-
|
|
73
|
-
variables: clone
|
|
74
|
-
operationName,
|
|
75
|
-
extensions
|
|
61
|
+
...body,
|
|
62
|
+
variables: clone
|
|
76
63
|
})
|
|
77
64
|
);
|
|
78
65
|
form.append("map", JSON.stringify(map));
|
|
@@ -150,6 +137,20 @@ function createResultForAbort(reason, extensions) {
|
|
|
150
137
|
errors: [createGraphQLErrorForAbort(reason, extensions)]
|
|
151
138
|
};
|
|
152
139
|
}
|
|
140
|
+
function hashSHA256(str) {
|
|
141
|
+
const textEncoder = new fetch.TextEncoder();
|
|
142
|
+
const utf8 = textEncoder.encode(str);
|
|
143
|
+
return utils.mapMaybePromise(
|
|
144
|
+
fetch.crypto.subtle.digest("SHA-256", utf8),
|
|
145
|
+
(hashBuffer) => {
|
|
146
|
+
let hashHex = "";
|
|
147
|
+
for (const bytes of new Uint8Array(hashBuffer)) {
|
|
148
|
+
hashHex += bytes.toString(16).padStart(2, "0");
|
|
149
|
+
}
|
|
150
|
+
return hashHex;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
153
154
|
|
|
154
155
|
const DELIM = "\n\n";
|
|
155
156
|
function isReadableStream(value) {
|
|
@@ -252,23 +253,22 @@ const isLiveQueryOperationDefinitionNode = utils.memoize1(
|
|
|
252
253
|
|
|
253
254
|
function prepareGETUrl({
|
|
254
255
|
baseUrl = "",
|
|
255
|
-
|
|
256
|
-
variables,
|
|
257
|
-
operationName,
|
|
258
|
-
extensions
|
|
256
|
+
body
|
|
259
257
|
}) {
|
|
260
258
|
const dummyHostname = "https://dummyhostname.com";
|
|
261
259
|
const validUrl = baseUrl.startsWith("http") ? baseUrl : baseUrl?.startsWith("/") ? `${dummyHostname}${baseUrl}` : `${dummyHostname}/${baseUrl}`;
|
|
262
260
|
const urlObj = new URL(validUrl);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
261
|
+
if (body.query) {
|
|
262
|
+
urlObj.searchParams.set("query", body.query);
|
|
263
|
+
}
|
|
264
|
+
if (body.variables && Object.keys(body.variables).length > 0) {
|
|
265
|
+
urlObj.searchParams.set("variables", JSON.stringify(body.variables));
|
|
266
266
|
}
|
|
267
|
-
if (operationName) {
|
|
268
|
-
urlObj.searchParams.set("operationName", operationName);
|
|
267
|
+
if (body.operationName) {
|
|
268
|
+
urlObj.searchParams.set("operationName", body.operationName);
|
|
269
269
|
}
|
|
270
|
-
if (extensions) {
|
|
271
|
-
urlObj.searchParams.set("extensions", JSON.stringify(extensions));
|
|
270
|
+
if (body.extensions) {
|
|
271
|
+
urlObj.searchParams.set("extensions", JSON.stringify(body.extensions));
|
|
272
272
|
}
|
|
273
273
|
const finalUrl = urlObj.toString().replace(dummyHostname, "");
|
|
274
274
|
return finalUrl;
|
|
@@ -294,7 +294,7 @@ function buildHTTPExecutor(options) {
|
|
|
294
294
|
const printFn = options?.print ?? defaultPrintFn;
|
|
295
295
|
const disposeCtrl = new AbortController();
|
|
296
296
|
const sharedSignal = createSignalWrapper(disposeCtrl.signal);
|
|
297
|
-
const baseExecutor = (request) => {
|
|
297
|
+
const baseExecutor = (request, excludeQuery) => {
|
|
298
298
|
if (sharedSignal.aborted) {
|
|
299
299
|
return createResultForAbort(sharedSignal.reason);
|
|
300
300
|
}
|
|
@@ -325,7 +325,6 @@ function buildHTTPExecutor(options) {
|
|
|
325
325
|
Object.assign(headers, headersFromExtensions);
|
|
326
326
|
request.extensions = restExtensions;
|
|
327
327
|
}
|
|
328
|
-
const query = printFn(request.document);
|
|
329
328
|
let signal = sharedSignal;
|
|
330
329
|
if (options?.timeout) {
|
|
331
330
|
signal = AbortSignal.any([
|
|
@@ -339,163 +338,204 @@ function buildHTTPExecutor(options) {
|
|
|
339
338
|
},
|
|
340
339
|
response: {}
|
|
341
340
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
341
|
+
const query = printFn(request.document);
|
|
342
|
+
let serializeFn = function serialize() {
|
|
343
|
+
return {
|
|
344
|
+
query: excludeQuery ? void 0 : printFn(request.document),
|
|
345
|
+
variables: (request.variables && Object.keys(request.variables).length) > 0 ? request.variables : void 0,
|
|
346
|
+
operationName: request.operationName ? request.operationName : void 0,
|
|
347
|
+
extensions: request.extensions && Object.keys(request.extensions).length > 0 ? request.extensions : void 0
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
if (options?.apq) {
|
|
351
|
+
serializeFn = function serializeWithAPQ() {
|
|
352
|
+
return utils.mapMaybePromise(hashSHA256(query), (sha256Hash) => {
|
|
353
|
+
const extensions = request.extensions || {};
|
|
354
|
+
extensions["persistedQuery"] = {
|
|
355
|
+
version: 1,
|
|
356
|
+
sha256Hash
|
|
356
357
|
};
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
case "POST": {
|
|
364
|
-
const body = {
|
|
365
|
-
query,
|
|
366
|
-
variables: request.variables,
|
|
367
|
-
operationName: request.operationName,
|
|
368
|
-
extensions: request.extensions
|
|
358
|
+
return {
|
|
359
|
+
query: excludeQuery ? void 0 : query,
|
|
360
|
+
variables: (request.variables && Object.keys(request.variables).length) > 0 ? request.variables : void 0,
|
|
361
|
+
operationName: request.operationName ? request.operationName : void 0,
|
|
362
|
+
extensions
|
|
369
363
|
};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
return fetchFn(
|
|
391
|
-
endpoint,
|
|
392
|
-
fetchOptions,
|
|
393
|
-
request.context,
|
|
394
|
-
request.info
|
|
395
|
-
);
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return utils.mapMaybePromise(
|
|
368
|
+
serializeFn(),
|
|
369
|
+
(body) => new valueOrPromise.ValueOrPromise(() => {
|
|
370
|
+
switch (method) {
|
|
371
|
+
case "GET": {
|
|
372
|
+
const finalUrl = prepareGETUrl({
|
|
373
|
+
baseUrl: endpoint,
|
|
374
|
+
body
|
|
375
|
+
});
|
|
376
|
+
const fetchOptions = {
|
|
377
|
+
method: "GET",
|
|
378
|
+
headers,
|
|
379
|
+
signal
|
|
380
|
+
};
|
|
381
|
+
if (options?.credentials != null) {
|
|
382
|
+
fetchOptions.credentials = options.credentials;
|
|
396
383
|
}
|
|
384
|
+
upstreamErrorExtensions.request.url = finalUrl;
|
|
385
|
+
return fetchFn(
|
|
386
|
+
finalUrl,
|
|
387
|
+
fetchOptions,
|
|
388
|
+
request.context,
|
|
389
|
+
request.info
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
case "POST": {
|
|
393
|
+
upstreamErrorExtensions.request.body = body;
|
|
394
|
+
return utils.mapMaybePromise(
|
|
395
|
+
createFormDataFromVariables(body, {
|
|
396
|
+
File: options?.File,
|
|
397
|
+
FormData: options?.FormData
|
|
398
|
+
}),
|
|
399
|
+
(body2) => {
|
|
400
|
+
if (typeof body2 === "string" && !headers["content-type"]) {
|
|
401
|
+
upstreamErrorExtensions.request.body = body2;
|
|
402
|
+
headers["content-type"] = "application/json";
|
|
403
|
+
}
|
|
404
|
+
const fetchOptions = {
|
|
405
|
+
method: "POST",
|
|
406
|
+
body: body2,
|
|
407
|
+
headers,
|
|
408
|
+
signal
|
|
409
|
+
};
|
|
410
|
+
if (options?.credentials != null) {
|
|
411
|
+
fetchOptions.credentials = options.credentials;
|
|
412
|
+
}
|
|
413
|
+
return fetchFn(
|
|
414
|
+
endpoint,
|
|
415
|
+
fetchOptions,
|
|
416
|
+
request.context,
|
|
417
|
+
request.info
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}).then((fetchResult) => {
|
|
424
|
+
upstreamErrorExtensions.response.status = fetchResult.status;
|
|
425
|
+
upstreamErrorExtensions.response.statusText = fetchResult.statusText;
|
|
426
|
+
Object.defineProperty(upstreamErrorExtensions.response, "headers", {
|
|
427
|
+
get() {
|
|
428
|
+
return Object.fromEntries(fetchResult.headers.entries());
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}`
|
|
397
434
|
);
|
|
398
435
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
get() {
|
|
405
|
-
return Object.fromEntries(fetchResult.headers.entries());
|
|
436
|
+
const contentType = fetchResult.headers.get("content-type");
|
|
437
|
+
if (contentType?.includes("text/event-stream")) {
|
|
438
|
+
return handleEventStreamResponse(signal, fetchResult);
|
|
439
|
+
} else if (contentType?.includes("multipart/mixed")) {
|
|
440
|
+
return handleMultipartMixedResponse(fetchResult);
|
|
406
441
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
442
|
+
return fetchResult.text();
|
|
443
|
+
}).then((result) => {
|
|
444
|
+
if (typeof result === "string") {
|
|
445
|
+
upstreamErrorExtensions.response.body = result;
|
|
446
|
+
if (result) {
|
|
447
|
+
try {
|
|
448
|
+
const parsedResult = JSON.parse(result);
|
|
449
|
+
upstreamErrorExtensions.response.body = parsedResult;
|
|
450
|
+
if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) {
|
|
451
|
+
return {
|
|
452
|
+
errors: [
|
|
453
|
+
utils.createGraphQLError(
|
|
454
|
+
'Unexpected empty "data" and "errors" fields in result: ' + result,
|
|
455
|
+
{
|
|
456
|
+
extensions: upstreamErrorExtensions
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
]
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(parsedResult.errors)) {
|
|
463
|
+
return {
|
|
464
|
+
...parsedResult,
|
|
465
|
+
errors: parsedResult.errors.map(
|
|
466
|
+
({
|
|
467
|
+
message,
|
|
468
|
+
...options2
|
|
469
|
+
}) => utils.createGraphQLError(message, {
|
|
470
|
+
...options2,
|
|
471
|
+
extensions: {
|
|
472
|
+
code: "DOWNSTREAM_SERVICE_ERROR",
|
|
473
|
+
...options2.extensions || {}
|
|
474
|
+
}
|
|
475
|
+
})
|
|
476
|
+
)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return parsedResult;
|
|
480
|
+
} catch (e) {
|
|
428
481
|
return {
|
|
429
482
|
errors: [
|
|
430
483
|
utils.createGraphQLError(
|
|
431
|
-
|
|
484
|
+
`Unexpected response: ${JSON.stringify(result)}`,
|
|
432
485
|
{
|
|
433
|
-
extensions: upstreamErrorExtensions
|
|
486
|
+
extensions: upstreamErrorExtensions,
|
|
487
|
+
originalError: e
|
|
434
488
|
}
|
|
435
489
|
)
|
|
436
490
|
]
|
|
437
491
|
};
|
|
438
492
|
}
|
|
439
|
-
if (Array.isArray(parsedResult.errors)) {
|
|
440
|
-
return {
|
|
441
|
-
...parsedResult,
|
|
442
|
-
errors: parsedResult.errors.map(
|
|
443
|
-
({
|
|
444
|
-
message,
|
|
445
|
-
...options2
|
|
446
|
-
}) => utils.createGraphQLError(message, {
|
|
447
|
-
...options2,
|
|
448
|
-
extensions: {
|
|
449
|
-
code: "DOWNSTREAM_SERVICE_ERROR",
|
|
450
|
-
...options2.extensions || {}
|
|
451
|
-
}
|
|
452
|
-
})
|
|
453
|
-
)
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
return parsedResult;
|
|
457
|
-
} catch (e) {
|
|
458
|
-
return {
|
|
459
|
-
errors: [
|
|
460
|
-
utils.createGraphQLError(
|
|
461
|
-
`Unexpected response: ${JSON.stringify(result)}`,
|
|
462
|
-
{
|
|
463
|
-
extensions: upstreamErrorExtensions,
|
|
464
|
-
originalError: e
|
|
465
|
-
}
|
|
466
|
-
)
|
|
467
|
-
]
|
|
468
|
-
};
|
|
469
493
|
}
|
|
494
|
+
} else {
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
}).catch((e) => {
|
|
498
|
+
if (e.name === "AggregateError") {
|
|
499
|
+
return {
|
|
500
|
+
errors: e.errors.map(
|
|
501
|
+
(e2) => coerceFetchError(e2, {
|
|
502
|
+
signal,
|
|
503
|
+
endpoint,
|
|
504
|
+
upstreamErrorExtensions
|
|
505
|
+
})
|
|
506
|
+
)
|
|
507
|
+
};
|
|
470
508
|
}
|
|
471
|
-
} else {
|
|
472
|
-
return result;
|
|
473
|
-
}
|
|
474
|
-
}).catch((e) => {
|
|
475
|
-
if (e.name === "AggregateError") {
|
|
476
509
|
return {
|
|
477
|
-
errors:
|
|
478
|
-
|
|
510
|
+
errors: [
|
|
511
|
+
coerceFetchError(e, {
|
|
479
512
|
signal,
|
|
480
513
|
endpoint,
|
|
481
514
|
upstreamErrorExtensions
|
|
482
515
|
})
|
|
483
|
-
|
|
516
|
+
]
|
|
484
517
|
};
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
errors: [
|
|
488
|
-
coerceFetchError(e, {
|
|
489
|
-
signal,
|
|
490
|
-
endpoint,
|
|
491
|
-
upstreamErrorExtensions
|
|
492
|
-
})
|
|
493
|
-
]
|
|
494
|
-
};
|
|
495
|
-
}).resolve();
|
|
518
|
+
}).resolve()
|
|
519
|
+
);
|
|
496
520
|
};
|
|
497
521
|
let executor = baseExecutor;
|
|
522
|
+
if (options?.apq != null) {
|
|
523
|
+
executor = function apqExecutor(request) {
|
|
524
|
+
return utils.mapMaybePromise(
|
|
525
|
+
baseExecutor(request, true),
|
|
526
|
+
(res) => {
|
|
527
|
+
if (res.errors?.some(
|
|
528
|
+
(error) => error.extensions["code"] === "PERSISTED_QUERY_NOT_FOUND" || error.message === "PersistedQueryNotFound"
|
|
529
|
+
)) {
|
|
530
|
+
return baseExecutor(request, false);
|
|
531
|
+
}
|
|
532
|
+
return res;
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
}
|
|
498
537
|
if (options?.retry != null) {
|
|
538
|
+
const prevExecutor = executor;
|
|
499
539
|
executor = function retryExecutor(request) {
|
|
500
540
|
let result;
|
|
501
541
|
let attempt = 0;
|
|
@@ -512,7 +552,7 @@ function buildHTTPExecutor(options) {
|
|
|
512
552
|
errors: [utils.createGraphQLError("No response returned from fetch")]
|
|
513
553
|
};
|
|
514
554
|
}
|
|
515
|
-
return utils.mapMaybePromise(
|
|
555
|
+
return utils.mapMaybePromise(prevExecutor(request), (res) => {
|
|
516
556
|
result = res;
|
|
517
557
|
if (result?.errors?.length) {
|
|
518
558
|
return retryAttempt();
|
package/dist/index.d.cts
CHANGED
|
@@ -14,10 +14,20 @@ type FetchFn = AsyncFetchFn | SyncFetchFn | RegularFetchFn;
|
|
|
14
14
|
type AsyncImportFn = (moduleName: string) => PromiseLike<any>;
|
|
15
15
|
type SyncImportFn = (moduleName: string) => any;
|
|
16
16
|
interface HTTPExecutorOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The endpoint to use when querying the upstream API
|
|
19
|
+
* @default '/graphql'
|
|
20
|
+
*/
|
|
17
21
|
endpoint?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The WHATWG compatible fetch implementation to use
|
|
24
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
|
25
|
+
* @default globalThis.fetch
|
|
26
|
+
*/
|
|
18
27
|
fetch?: FetchFn;
|
|
19
28
|
/**
|
|
20
29
|
* Whether to use the GET HTTP method for queries when querying the original schema
|
|
30
|
+
* @default false
|
|
21
31
|
*/
|
|
22
32
|
useGETForQueries?: boolean;
|
|
23
33
|
/**
|
|
@@ -25,7 +35,8 @@ interface HTTPExecutorOptions {
|
|
|
25
35
|
*/
|
|
26
36
|
headers?: HeadersConfig | ((executorRequest?: ExecutionRequest) => HeadersConfig);
|
|
27
37
|
/**
|
|
28
|
-
* HTTP method to use when querying the original schema.
|
|
38
|
+
* HTTP method to use when querying the original schema.x
|
|
39
|
+
* @default 'POST'
|
|
29
40
|
*/
|
|
30
41
|
method?: 'GET' | 'POST';
|
|
31
42
|
/**
|
|
@@ -33,7 +44,8 @@ interface HTTPExecutorOptions {
|
|
|
33
44
|
*/
|
|
34
45
|
timeout?: number;
|
|
35
46
|
/**
|
|
36
|
-
* Request Credentials
|
|
47
|
+
* Request Credentials
|
|
48
|
+
* @default 'same-origin'
|
|
37
49
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
|
38
50
|
*/
|
|
39
51
|
credentials?: RequestCredentials;
|
|
@@ -42,25 +54,38 @@ interface HTTPExecutorOptions {
|
|
|
42
54
|
*/
|
|
43
55
|
retry?: number;
|
|
44
56
|
/**
|
|
45
|
-
* WHATWG compatible File implementation
|
|
57
|
+
* WHATWG compatible `File` implementation
|
|
46
58
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/File
|
|
47
59
|
*/
|
|
48
60
|
File?: typeof File;
|
|
49
61
|
/**
|
|
50
|
-
* WHATWG compatible FormData implementation
|
|
62
|
+
* WHATWG compatible `FormData` implementation
|
|
51
63
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/FormData
|
|
52
64
|
*/
|
|
53
65
|
FormData?: typeof FormData;
|
|
54
66
|
/**
|
|
55
|
-
* Print function for DocumentNode
|
|
67
|
+
* Print function for `DocumentNode`
|
|
68
|
+
* Useful when you want to memoize the print function or use a different implementation to minify the query etc.
|
|
56
69
|
*/
|
|
57
70
|
print?: (doc: DocumentNode) => string;
|
|
58
71
|
/**
|
|
59
|
-
* Enable
|
|
72
|
+
* Enable Automatic Persisted Queries
|
|
73
|
+
* @see https://www.apollographql.com/docs/apollo-server/performance/apq/
|
|
74
|
+
*/
|
|
75
|
+
apq?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Enable Explicit Resource Management
|
|
78
|
+
* @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management
|
|
60
79
|
* @deprecated The executors are always disposable, and this option will be removed in the next major version, there is no need to have a flag for this.
|
|
61
80
|
*/
|
|
62
81
|
disposable?: boolean;
|
|
63
82
|
}
|
|
83
|
+
type SerializedRequest = {
|
|
84
|
+
query?: string;
|
|
85
|
+
variables?: Record<string, any>;
|
|
86
|
+
operationName?: string;
|
|
87
|
+
extensions?: any;
|
|
88
|
+
};
|
|
64
89
|
type HeadersConfig = Record<string, string>;
|
|
65
90
|
declare function buildHTTPExecutor(options?: Omit<HTTPExecutorOptions, 'fetch'> & {
|
|
66
91
|
fetch: SyncFetchFn;
|
|
@@ -73,4 +98,4 @@ declare function buildHTTPExecutor(options?: Omit<HTTPExecutorOptions, 'fetch'>
|
|
|
73
98
|
}): DisposableAsyncExecutor<any, HTTPExecutorOptions>;
|
|
74
99
|
declare function buildHTTPExecutor(options?: Omit<HTTPExecutorOptions, 'fetch'>): DisposableAsyncExecutor<any, HTTPExecutorOptions>;
|
|
75
100
|
|
|
76
|
-
export { type AsyncFetchFn, type AsyncImportFn, type FetchFn, type HTTPExecutorOptions, type HeadersConfig, type RegularFetchFn, type SyncFetchFn, type SyncImportFn, type SyncResponse, buildHTTPExecutor, isLiveQueryOperationDefinitionNode };
|
|
101
|
+
export { type AsyncFetchFn, type AsyncImportFn, type FetchFn, type HTTPExecutorOptions, type HeadersConfig, type RegularFetchFn, type SerializedRequest, type SyncFetchFn, type SyncImportFn, type SyncResponse, buildHTTPExecutor, isLiveQueryOperationDefinitionNode };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,10 +14,20 @@ type FetchFn = AsyncFetchFn | SyncFetchFn | RegularFetchFn;
|
|
|
14
14
|
type AsyncImportFn = (moduleName: string) => PromiseLike<any>;
|
|
15
15
|
type SyncImportFn = (moduleName: string) => any;
|
|
16
16
|
interface HTTPExecutorOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The endpoint to use when querying the upstream API
|
|
19
|
+
* @default '/graphql'
|
|
20
|
+
*/
|
|
17
21
|
endpoint?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The WHATWG compatible fetch implementation to use
|
|
24
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
|
25
|
+
* @default globalThis.fetch
|
|
26
|
+
*/
|
|
18
27
|
fetch?: FetchFn;
|
|
19
28
|
/**
|
|
20
29
|
* Whether to use the GET HTTP method for queries when querying the original schema
|
|
30
|
+
* @default false
|
|
21
31
|
*/
|
|
22
32
|
useGETForQueries?: boolean;
|
|
23
33
|
/**
|
|
@@ -25,7 +35,8 @@ interface HTTPExecutorOptions {
|
|
|
25
35
|
*/
|
|
26
36
|
headers?: HeadersConfig | ((executorRequest?: ExecutionRequest) => HeadersConfig);
|
|
27
37
|
/**
|
|
28
|
-
* HTTP method to use when querying the original schema.
|
|
38
|
+
* HTTP method to use when querying the original schema.x
|
|
39
|
+
* @default 'POST'
|
|
29
40
|
*/
|
|
30
41
|
method?: 'GET' | 'POST';
|
|
31
42
|
/**
|
|
@@ -33,7 +44,8 @@ interface HTTPExecutorOptions {
|
|
|
33
44
|
*/
|
|
34
45
|
timeout?: number;
|
|
35
46
|
/**
|
|
36
|
-
* Request Credentials
|
|
47
|
+
* Request Credentials
|
|
48
|
+
* @default 'same-origin'
|
|
37
49
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
|
38
50
|
*/
|
|
39
51
|
credentials?: RequestCredentials;
|
|
@@ -42,25 +54,38 @@ interface HTTPExecutorOptions {
|
|
|
42
54
|
*/
|
|
43
55
|
retry?: number;
|
|
44
56
|
/**
|
|
45
|
-
* WHATWG compatible File implementation
|
|
57
|
+
* WHATWG compatible `File` implementation
|
|
46
58
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/File
|
|
47
59
|
*/
|
|
48
60
|
File?: typeof File;
|
|
49
61
|
/**
|
|
50
|
-
* WHATWG compatible FormData implementation
|
|
62
|
+
* WHATWG compatible `FormData` implementation
|
|
51
63
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/FormData
|
|
52
64
|
*/
|
|
53
65
|
FormData?: typeof FormData;
|
|
54
66
|
/**
|
|
55
|
-
* Print function for DocumentNode
|
|
67
|
+
* Print function for `DocumentNode`
|
|
68
|
+
* Useful when you want to memoize the print function or use a different implementation to minify the query etc.
|
|
56
69
|
*/
|
|
57
70
|
print?: (doc: DocumentNode) => string;
|
|
58
71
|
/**
|
|
59
|
-
* Enable
|
|
72
|
+
* Enable Automatic Persisted Queries
|
|
73
|
+
* @see https://www.apollographql.com/docs/apollo-server/performance/apq/
|
|
74
|
+
*/
|
|
75
|
+
apq?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Enable Explicit Resource Management
|
|
78
|
+
* @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management
|
|
60
79
|
* @deprecated The executors are always disposable, and this option will be removed in the next major version, there is no need to have a flag for this.
|
|
61
80
|
*/
|
|
62
81
|
disposable?: boolean;
|
|
63
82
|
}
|
|
83
|
+
type SerializedRequest = {
|
|
84
|
+
query?: string;
|
|
85
|
+
variables?: Record<string, any>;
|
|
86
|
+
operationName?: string;
|
|
87
|
+
extensions?: any;
|
|
88
|
+
};
|
|
64
89
|
type HeadersConfig = Record<string, string>;
|
|
65
90
|
declare function buildHTTPExecutor(options?: Omit<HTTPExecutorOptions, 'fetch'> & {
|
|
66
91
|
fetch: SyncFetchFn;
|
|
@@ -73,4 +98,4 @@ declare function buildHTTPExecutor(options?: Omit<HTTPExecutorOptions, 'fetch'>
|
|
|
73
98
|
}): DisposableAsyncExecutor<any, HTTPExecutorOptions>;
|
|
74
99
|
declare function buildHTTPExecutor(options?: Omit<HTTPExecutorOptions, 'fetch'>): DisposableAsyncExecutor<any, HTTPExecutorOptions>;
|
|
75
100
|
|
|
76
|
-
export { type AsyncFetchFn, type AsyncImportFn, type FetchFn, type HTTPExecutorOptions, type HeadersConfig, type RegularFetchFn, type SyncFetchFn, type SyncImportFn, type SyncResponse, buildHTTPExecutor, isLiveQueryOperationDefinitionNode };
|
|
101
|
+
export { type AsyncFetchFn, type AsyncImportFn, type FetchFn, type HTTPExecutorOptions, type HeadersConfig, type RegularFetchFn, type SerializedRequest, type SyncFetchFn, type SyncImportFn, type SyncResponse, buildHTTPExecutor, isLiveQueryOperationDefinitionNode };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isAsyncIterable, isPromise, mapMaybePromise, memoize1, createGraphQLError, inspect, mapAsyncIterator, mergeIncrementalResult, getOperationASTFromRequest } from '@graphql-tools/utils';
|
|
2
2
|
import { DisposableSymbols } from '@whatwg-node/disposablestack';
|
|
3
|
-
import { File, FormData, TextDecoder, fetch } from '@whatwg-node/fetch';
|
|
3
|
+
import { File, FormData, TextEncoder, crypto, TextDecoder, fetch } from '@whatwg-node/fetch';
|
|
4
4
|
import { ValueOrPromise } from 'value-or-promise';
|
|
5
5
|
import { extractFiles, isExtractableFile } from 'extract-files';
|
|
6
6
|
import { stripIgnoredCharacters, print } from 'graphql';
|
|
@@ -28,32 +28,21 @@ function collectAsyncIterableValues(asyncIterable) {
|
|
|
28
28
|
}
|
|
29
29
|
return iterate();
|
|
30
30
|
}
|
|
31
|
-
function createFormDataFromVariables({
|
|
32
|
-
query,
|
|
33
|
-
variables,
|
|
34
|
-
operationName,
|
|
35
|
-
extensions
|
|
36
|
-
}, {
|
|
31
|
+
function createFormDataFromVariables(body, {
|
|
37
32
|
File: FileCtor = File,
|
|
38
33
|
FormData: FormDataCtor = FormData
|
|
39
34
|
}) {
|
|
40
|
-
|
|
35
|
+
if (!body.variables) {
|
|
36
|
+
return JSON.stringify(body);
|
|
37
|
+
}
|
|
38
|
+
const vars = Object.assign({}, body.variables);
|
|
41
39
|
const { clone, files } = extractFiles(
|
|
42
40
|
vars,
|
|
43
41
|
"variables",
|
|
44
42
|
(v) => isExtractableFile(v) || v?.promise || isAsyncIterable(v) || v?.then || typeof v?.arrayBuffer === "function"
|
|
45
43
|
);
|
|
46
44
|
if (files.size === 0) {
|
|
47
|
-
return JSON.stringify(
|
|
48
|
-
{
|
|
49
|
-
query,
|
|
50
|
-
variables,
|
|
51
|
-
operationName,
|
|
52
|
-
extensions
|
|
53
|
-
},
|
|
54
|
-
null,
|
|
55
|
-
2
|
|
56
|
-
);
|
|
45
|
+
return JSON.stringify(body);
|
|
57
46
|
}
|
|
58
47
|
const map = {};
|
|
59
48
|
const uploads = [];
|
|
@@ -67,10 +56,8 @@ function createFormDataFromVariables({
|
|
|
67
56
|
form.append(
|
|
68
57
|
"operations",
|
|
69
58
|
JSON.stringify({
|
|
70
|
-
|
|
71
|
-
variables: clone
|
|
72
|
-
operationName,
|
|
73
|
-
extensions
|
|
59
|
+
...body,
|
|
60
|
+
variables: clone
|
|
74
61
|
})
|
|
75
62
|
);
|
|
76
63
|
form.append("map", JSON.stringify(map));
|
|
@@ -148,6 +135,20 @@ function createResultForAbort(reason, extensions) {
|
|
|
148
135
|
errors: [createGraphQLErrorForAbort(reason, extensions)]
|
|
149
136
|
};
|
|
150
137
|
}
|
|
138
|
+
function hashSHA256(str) {
|
|
139
|
+
const textEncoder = new TextEncoder();
|
|
140
|
+
const utf8 = textEncoder.encode(str);
|
|
141
|
+
return mapMaybePromise(
|
|
142
|
+
crypto.subtle.digest("SHA-256", utf8),
|
|
143
|
+
(hashBuffer) => {
|
|
144
|
+
let hashHex = "";
|
|
145
|
+
for (const bytes of new Uint8Array(hashBuffer)) {
|
|
146
|
+
hashHex += bytes.toString(16).padStart(2, "0");
|
|
147
|
+
}
|
|
148
|
+
return hashHex;
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
}
|
|
151
152
|
|
|
152
153
|
const DELIM = "\n\n";
|
|
153
154
|
function isReadableStream(value) {
|
|
@@ -250,23 +251,22 @@ const isLiveQueryOperationDefinitionNode = memoize1(
|
|
|
250
251
|
|
|
251
252
|
function prepareGETUrl({
|
|
252
253
|
baseUrl = "",
|
|
253
|
-
|
|
254
|
-
variables,
|
|
255
|
-
operationName,
|
|
256
|
-
extensions
|
|
254
|
+
body
|
|
257
255
|
}) {
|
|
258
256
|
const dummyHostname = "https://dummyhostname.com";
|
|
259
257
|
const validUrl = baseUrl.startsWith("http") ? baseUrl : baseUrl?.startsWith("/") ? `${dummyHostname}${baseUrl}` : `${dummyHostname}/${baseUrl}`;
|
|
260
258
|
const urlObj = new URL(validUrl);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
if (body.query) {
|
|
260
|
+
urlObj.searchParams.set("query", body.query);
|
|
261
|
+
}
|
|
262
|
+
if (body.variables && Object.keys(body.variables).length > 0) {
|
|
263
|
+
urlObj.searchParams.set("variables", JSON.stringify(body.variables));
|
|
264
264
|
}
|
|
265
|
-
if (operationName) {
|
|
266
|
-
urlObj.searchParams.set("operationName", operationName);
|
|
265
|
+
if (body.operationName) {
|
|
266
|
+
urlObj.searchParams.set("operationName", body.operationName);
|
|
267
267
|
}
|
|
268
|
-
if (extensions) {
|
|
269
|
-
urlObj.searchParams.set("extensions", JSON.stringify(extensions));
|
|
268
|
+
if (body.extensions) {
|
|
269
|
+
urlObj.searchParams.set("extensions", JSON.stringify(body.extensions));
|
|
270
270
|
}
|
|
271
271
|
const finalUrl = urlObj.toString().replace(dummyHostname, "");
|
|
272
272
|
return finalUrl;
|
|
@@ -292,7 +292,7 @@ function buildHTTPExecutor(options) {
|
|
|
292
292
|
const printFn = options?.print ?? defaultPrintFn;
|
|
293
293
|
const disposeCtrl = new AbortController();
|
|
294
294
|
const sharedSignal = createSignalWrapper(disposeCtrl.signal);
|
|
295
|
-
const baseExecutor = (request) => {
|
|
295
|
+
const baseExecutor = (request, excludeQuery) => {
|
|
296
296
|
if (sharedSignal.aborted) {
|
|
297
297
|
return createResultForAbort(sharedSignal.reason);
|
|
298
298
|
}
|
|
@@ -323,7 +323,6 @@ function buildHTTPExecutor(options) {
|
|
|
323
323
|
Object.assign(headers, headersFromExtensions);
|
|
324
324
|
request.extensions = restExtensions;
|
|
325
325
|
}
|
|
326
|
-
const query = printFn(request.document);
|
|
327
326
|
let signal = sharedSignal;
|
|
328
327
|
if (options?.timeout) {
|
|
329
328
|
signal = AbortSignal.any([
|
|
@@ -337,163 +336,204 @@ function buildHTTPExecutor(options) {
|
|
|
337
336
|
},
|
|
338
337
|
response: {}
|
|
339
338
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
339
|
+
const query = printFn(request.document);
|
|
340
|
+
let serializeFn = function serialize() {
|
|
341
|
+
return {
|
|
342
|
+
query: excludeQuery ? void 0 : printFn(request.document),
|
|
343
|
+
variables: (request.variables && Object.keys(request.variables).length) > 0 ? request.variables : void 0,
|
|
344
|
+
operationName: request.operationName ? request.operationName : void 0,
|
|
345
|
+
extensions: request.extensions && Object.keys(request.extensions).length > 0 ? request.extensions : void 0
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
if (options?.apq) {
|
|
349
|
+
serializeFn = function serializeWithAPQ() {
|
|
350
|
+
return mapMaybePromise(hashSHA256(query), (sha256Hash) => {
|
|
351
|
+
const extensions = request.extensions || {};
|
|
352
|
+
extensions["persistedQuery"] = {
|
|
353
|
+
version: 1,
|
|
354
|
+
sha256Hash
|
|
354
355
|
};
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
case "POST": {
|
|
362
|
-
const body = {
|
|
363
|
-
query,
|
|
364
|
-
variables: request.variables,
|
|
365
|
-
operationName: request.operationName,
|
|
366
|
-
extensions: request.extensions
|
|
356
|
+
return {
|
|
357
|
+
query: excludeQuery ? void 0 : query,
|
|
358
|
+
variables: (request.variables && Object.keys(request.variables).length) > 0 ? request.variables : void 0,
|
|
359
|
+
operationName: request.operationName ? request.operationName : void 0,
|
|
360
|
+
extensions
|
|
367
361
|
};
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
return fetchFn(
|
|
389
|
-
endpoint,
|
|
390
|
-
fetchOptions,
|
|
391
|
-
request.context,
|
|
392
|
-
request.info
|
|
393
|
-
);
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return mapMaybePromise(
|
|
366
|
+
serializeFn(),
|
|
367
|
+
(body) => new ValueOrPromise(() => {
|
|
368
|
+
switch (method) {
|
|
369
|
+
case "GET": {
|
|
370
|
+
const finalUrl = prepareGETUrl({
|
|
371
|
+
baseUrl: endpoint,
|
|
372
|
+
body
|
|
373
|
+
});
|
|
374
|
+
const fetchOptions = {
|
|
375
|
+
method: "GET",
|
|
376
|
+
headers,
|
|
377
|
+
signal
|
|
378
|
+
};
|
|
379
|
+
if (options?.credentials != null) {
|
|
380
|
+
fetchOptions.credentials = options.credentials;
|
|
394
381
|
}
|
|
382
|
+
upstreamErrorExtensions.request.url = finalUrl;
|
|
383
|
+
return fetchFn(
|
|
384
|
+
finalUrl,
|
|
385
|
+
fetchOptions,
|
|
386
|
+
request.context,
|
|
387
|
+
request.info
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
case "POST": {
|
|
391
|
+
upstreamErrorExtensions.request.body = body;
|
|
392
|
+
return mapMaybePromise(
|
|
393
|
+
createFormDataFromVariables(body, {
|
|
394
|
+
File: options?.File,
|
|
395
|
+
FormData: options?.FormData
|
|
396
|
+
}),
|
|
397
|
+
(body2) => {
|
|
398
|
+
if (typeof body2 === "string" && !headers["content-type"]) {
|
|
399
|
+
upstreamErrorExtensions.request.body = body2;
|
|
400
|
+
headers["content-type"] = "application/json";
|
|
401
|
+
}
|
|
402
|
+
const fetchOptions = {
|
|
403
|
+
method: "POST",
|
|
404
|
+
body: body2,
|
|
405
|
+
headers,
|
|
406
|
+
signal
|
|
407
|
+
};
|
|
408
|
+
if (options?.credentials != null) {
|
|
409
|
+
fetchOptions.credentials = options.credentials;
|
|
410
|
+
}
|
|
411
|
+
return fetchFn(
|
|
412
|
+
endpoint,
|
|
413
|
+
fetchOptions,
|
|
414
|
+
request.context,
|
|
415
|
+
request.info
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}).then((fetchResult) => {
|
|
422
|
+
upstreamErrorExtensions.response.status = fetchResult.status;
|
|
423
|
+
upstreamErrorExtensions.response.statusText = fetchResult.statusText;
|
|
424
|
+
Object.defineProperty(upstreamErrorExtensions.response, "headers", {
|
|
425
|
+
get() {
|
|
426
|
+
return Object.fromEntries(fetchResult.headers.entries());
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}`
|
|
395
432
|
);
|
|
396
433
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
get() {
|
|
403
|
-
return Object.fromEntries(fetchResult.headers.entries());
|
|
434
|
+
const contentType = fetchResult.headers.get("content-type");
|
|
435
|
+
if (contentType?.includes("text/event-stream")) {
|
|
436
|
+
return handleEventStreamResponse(signal, fetchResult);
|
|
437
|
+
} else if (contentType?.includes("multipart/mixed")) {
|
|
438
|
+
return handleMultipartMixedResponse(fetchResult);
|
|
404
439
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
440
|
+
return fetchResult.text();
|
|
441
|
+
}).then((result) => {
|
|
442
|
+
if (typeof result === "string") {
|
|
443
|
+
upstreamErrorExtensions.response.body = result;
|
|
444
|
+
if (result) {
|
|
445
|
+
try {
|
|
446
|
+
const parsedResult = JSON.parse(result);
|
|
447
|
+
upstreamErrorExtensions.response.body = parsedResult;
|
|
448
|
+
if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) {
|
|
449
|
+
return {
|
|
450
|
+
errors: [
|
|
451
|
+
createGraphQLError(
|
|
452
|
+
'Unexpected empty "data" and "errors" fields in result: ' + result,
|
|
453
|
+
{
|
|
454
|
+
extensions: upstreamErrorExtensions
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (Array.isArray(parsedResult.errors)) {
|
|
461
|
+
return {
|
|
462
|
+
...parsedResult,
|
|
463
|
+
errors: parsedResult.errors.map(
|
|
464
|
+
({
|
|
465
|
+
message,
|
|
466
|
+
...options2
|
|
467
|
+
}) => createGraphQLError(message, {
|
|
468
|
+
...options2,
|
|
469
|
+
extensions: {
|
|
470
|
+
code: "DOWNSTREAM_SERVICE_ERROR",
|
|
471
|
+
...options2.extensions || {}
|
|
472
|
+
}
|
|
473
|
+
})
|
|
474
|
+
)
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return parsedResult;
|
|
478
|
+
} catch (e) {
|
|
426
479
|
return {
|
|
427
480
|
errors: [
|
|
428
481
|
createGraphQLError(
|
|
429
|
-
|
|
482
|
+
`Unexpected response: ${JSON.stringify(result)}`,
|
|
430
483
|
{
|
|
431
|
-
extensions: upstreamErrorExtensions
|
|
484
|
+
extensions: upstreamErrorExtensions,
|
|
485
|
+
originalError: e
|
|
432
486
|
}
|
|
433
487
|
)
|
|
434
488
|
]
|
|
435
489
|
};
|
|
436
490
|
}
|
|
437
|
-
if (Array.isArray(parsedResult.errors)) {
|
|
438
|
-
return {
|
|
439
|
-
...parsedResult,
|
|
440
|
-
errors: parsedResult.errors.map(
|
|
441
|
-
({
|
|
442
|
-
message,
|
|
443
|
-
...options2
|
|
444
|
-
}) => createGraphQLError(message, {
|
|
445
|
-
...options2,
|
|
446
|
-
extensions: {
|
|
447
|
-
code: "DOWNSTREAM_SERVICE_ERROR",
|
|
448
|
-
...options2.extensions || {}
|
|
449
|
-
}
|
|
450
|
-
})
|
|
451
|
-
)
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
return parsedResult;
|
|
455
|
-
} catch (e) {
|
|
456
|
-
return {
|
|
457
|
-
errors: [
|
|
458
|
-
createGraphQLError(
|
|
459
|
-
`Unexpected response: ${JSON.stringify(result)}`,
|
|
460
|
-
{
|
|
461
|
-
extensions: upstreamErrorExtensions,
|
|
462
|
-
originalError: e
|
|
463
|
-
}
|
|
464
|
-
)
|
|
465
|
-
]
|
|
466
|
-
};
|
|
467
491
|
}
|
|
492
|
+
} else {
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
}).catch((e) => {
|
|
496
|
+
if (e.name === "AggregateError") {
|
|
497
|
+
return {
|
|
498
|
+
errors: e.errors.map(
|
|
499
|
+
(e2) => coerceFetchError(e2, {
|
|
500
|
+
signal,
|
|
501
|
+
endpoint,
|
|
502
|
+
upstreamErrorExtensions
|
|
503
|
+
})
|
|
504
|
+
)
|
|
505
|
+
};
|
|
468
506
|
}
|
|
469
|
-
} else {
|
|
470
|
-
return result;
|
|
471
|
-
}
|
|
472
|
-
}).catch((e) => {
|
|
473
|
-
if (e.name === "AggregateError") {
|
|
474
507
|
return {
|
|
475
|
-
errors:
|
|
476
|
-
|
|
508
|
+
errors: [
|
|
509
|
+
coerceFetchError(e, {
|
|
477
510
|
signal,
|
|
478
511
|
endpoint,
|
|
479
512
|
upstreamErrorExtensions
|
|
480
513
|
})
|
|
481
|
-
|
|
514
|
+
]
|
|
482
515
|
};
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
errors: [
|
|
486
|
-
coerceFetchError(e, {
|
|
487
|
-
signal,
|
|
488
|
-
endpoint,
|
|
489
|
-
upstreamErrorExtensions
|
|
490
|
-
})
|
|
491
|
-
]
|
|
492
|
-
};
|
|
493
|
-
}).resolve();
|
|
516
|
+
}).resolve()
|
|
517
|
+
);
|
|
494
518
|
};
|
|
495
519
|
let executor = baseExecutor;
|
|
520
|
+
if (options?.apq != null) {
|
|
521
|
+
executor = function apqExecutor(request) {
|
|
522
|
+
return mapMaybePromise(
|
|
523
|
+
baseExecutor(request, true),
|
|
524
|
+
(res) => {
|
|
525
|
+
if (res.errors?.some(
|
|
526
|
+
(error) => error.extensions["code"] === "PERSISTED_QUERY_NOT_FOUND" || error.message === "PersistedQueryNotFound"
|
|
527
|
+
)) {
|
|
528
|
+
return baseExecutor(request, false);
|
|
529
|
+
}
|
|
530
|
+
return res;
|
|
531
|
+
}
|
|
532
|
+
);
|
|
533
|
+
};
|
|
534
|
+
}
|
|
496
535
|
if (options?.retry != null) {
|
|
536
|
+
const prevExecutor = executor;
|
|
497
537
|
executor = function retryExecutor(request) {
|
|
498
538
|
let result;
|
|
499
539
|
let attempt = 0;
|
|
@@ -510,7 +550,7 @@ function buildHTTPExecutor(options) {
|
|
|
510
550
|
errors: [createGraphQLError("No response returned from fetch")]
|
|
511
551
|
};
|
|
512
552
|
}
|
|
513
|
-
return mapMaybePromise(
|
|
553
|
+
return mapMaybePromise(prevExecutor(request), (res) => {
|
|
514
554
|
result = res;
|
|
515
555
|
if (result?.errors?.length) {
|
|
516
556
|
return retryAttempt();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphql-tools/executor-http",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-alpha-8e436b6c005eab0d1484933b57b778269906f0f6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A set of utils for faster development of GraphQL tools",
|
|
6
6
|
"repository": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"value-or-promise": "^1.0.12"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@apollo/server": "^4.11.2",
|
|
52
53
|
"@types/extract-files": "8.1.3",
|
|
53
54
|
"@whatwg-node/disposablestack": "^0.0.5",
|
|
54
55
|
"graphql": "^16.9.0",
|