@graphql-tools/executor-http
Version:
A set of utils for faster development of GraphQL tools
247 lines (246 loc) • 10.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLiveQueryOperationDefinitionNode = exports.buildHTTPExecutor = void 0;
const utils_1 = require("@graphql-tools/utils");
const graphql_1 = require("graphql");
const isLiveQueryOperationDefinitionNode_js_1 = require("./isLiveQueryOperationDefinitionNode.js");
Object.defineProperty(exports, "isLiveQueryOperationDefinitionNode", { enumerable: true, get: function () { return isLiveQueryOperationDefinitionNode_js_1.isLiveQueryOperationDefinitionNode; } });
const prepareGETUrl_js_1 = require("./prepareGETUrl.js");
const value_or_promise_1 = require("value-or-promise");
const createFormDataFromVariables_js_1 = require("./createFormDataFromVariables.js");
const handleEventStreamResponse_js_1 = require("./handleEventStreamResponse.js");
const handleMultipartMixedResponse_js_1 = require("./handleMultipartMixedResponse.js");
const fetch_1 = require("@whatwg-node/fetch");
function buildHTTPExecutor(options) {
const executor = (request) => {
const fetchFn = request.extensions?.fetch ?? options?.fetch ?? fetch_1.fetch;
let controller;
let method = request.extensions?.method || options?.method || 'POST';
const operationAst = (0, utils_1.getOperationASTFromRequest)(request);
const operationType = operationAst.operation;
if ((options?.useGETForQueries || request.extensions?.useGETForQueries) && operationType === 'query') {
method = 'GET';
}
let accept = 'application/graphql-response+json, application/json, multipart/mixed';
if (operationType === 'subscription' || (0, isLiveQueryOperationDefinitionNode_js_1.isLiveQueryOperationDefinitionNode)(operationAst)) {
method = 'GET';
accept = 'text/event-stream';
}
const endpoint = request.extensions?.endpoint || options?.endpoint || '/graphql';
const headers = Object.assign({
accept,
}, (typeof options?.headers === 'function' ? options.headers(request) : options?.headers) || {}, request.extensions?.headers || {});
const query = (0, graphql_1.print)(request.document);
const requestBody = {
query,
variables: request.variables,
operationName: request.operationName,
extensions: request.extensions,
};
let timeoutId;
if (options?.timeout) {
controller = new AbortController();
timeoutId = setTimeout(() => {
if (!controller?.signal.aborted) {
controller?.abort('timeout');
}
}, options.timeout);
}
const responseDetailsForError = {};
return new value_or_promise_1.ValueOrPromise(() => {
switch (method) {
case 'GET': {
const finalUrl = (0, prepareGETUrl_js_1.prepareGETUrl)({
baseUrl: endpoint,
...requestBody,
});
return fetchFn(finalUrl, {
method: 'GET',
...(options?.credentials != null ? { credentials: options.credentials } : {}),
headers,
signal: controller?.signal,
}, request.context, request.info);
}
case 'POST':
return new value_or_promise_1.ValueOrPromise(() => (0, createFormDataFromVariables_js_1.createFormDataFromVariables)(requestBody, {
File: options?.File,
FormData: options?.FormData,
}))
.then(body => fetchFn(endpoint, {
method: 'POST',
...(options?.credentials != null ? { credentials: options.credentials } : {}),
body,
headers: {
...headers,
...(typeof body === 'string' ? { 'content-type': 'application/json' } : {}),
},
signal: controller?.signal,
}, request.context, request.info))
.resolve();
}
})
.then((fetchResult) => {
responseDetailsForError.status = fetchResult.status;
responseDetailsForError.statusText = fetchResult.statusText;
if (timeoutId != null) {
clearTimeout(timeoutId);
}
// Retry should respect HTTP Errors
if (options?.retry != null && !fetchResult.status.toString().startsWith('2')) {
throw new Error(fetchResult.statusText || `HTTP Error: ${fetchResult.status}`);
}
const contentType = fetchResult.headers.get('content-type');
if (contentType?.includes('text/event-stream')) {
return (0, handleEventStreamResponse_js_1.handleEventStreamResponse)(fetchResult, controller);
}
else if (contentType?.includes('multipart/mixed')) {
return (0, handleMultipartMixedResponse_js_1.handleMultipartMixedResponse)(fetchResult, controller);
}
return fetchResult.text();
})
.then(result => {
if (typeof result === 'string') {
if (result) {
try {
return JSON.parse(result);
}
catch (e) {
return {
errors: [
(0, utils_1.createGraphQLError)(`Unexpected response: ${JSON.stringify(result)}`, {
extensions: {
requestBody: {
query,
operationName: request.operationName,
},
responseDetails: responseDetailsForError,
},
originalError: e,
}),
],
};
}
}
}
else {
return result;
}
})
.catch((e) => {
if (typeof e === 'string') {
return {
errors: [
(0, utils_1.createGraphQLError)(e, {
extensions: {
requestBody: {
query,
operationName: request.operationName,
},
responseDetails: responseDetailsForError,
},
}),
],
};
}
else if (e.name === 'GraphQLError') {
return {
errors: [e],
};
}
else if (e.name === 'TypeError' && e.message === 'fetch failed') {
return {
errors: [
(0, utils_1.createGraphQLError)(`fetch failed to ${endpoint}`, {
extensions: {
requestBody: {
query,
operationName: request.operationName,
},
responseDetails: responseDetailsForError,
},
originalError: e,
}),
],
};
}
else if (e.name === 'AbortError' && controller?.signal?.reason) {
return {
errors: [
(0, utils_1.createGraphQLError)('The operation was aborted. reason: ' + controller.signal.reason, {
extensions: {
requestBody: {
query,
operationName: request.operationName,
},
responseDetails: responseDetailsForError,
},
originalError: e,
}),
],
};
}
else if (e.message) {
return {
errors: [
(0, utils_1.createGraphQLError)(e.message, {
extensions: {
requestBody: {
query,
operationName: request.operationName,
},
responseDetails: responseDetailsForError,
},
originalError: e,
}),
],
};
}
else {
return {
errors: [
(0, utils_1.createGraphQLError)('Unknown error', {
extensions: {
requestBody: {
query,
operationName: request.operationName,
},
responseDetails: responseDetailsForError,
},
originalError: e,
}),
],
};
}
})
.resolve();
};
if (options?.retry != null) {
return function retryExecutor(request) {
let result;
let attempt = 0;
function retryAttempt() {
attempt++;
if (attempt > options.retry) {
if (result != null) {
return result;
}
return {
errors: [(0, utils_1.createGraphQLError)('No response returned from fetch')],
};
}
return new value_or_promise_1.ValueOrPromise(() => executor(request))
.then(res => {
result = res;
if (result?.errors?.length) {
return retryAttempt();
}
return result;
})
.resolve();
}
return retryAttempt();
};
}
return executor;
}
exports.buildHTTPExecutor = buildHTTPExecutor;