UNPKG

@graphql-tools/executor-http

Version:

A set of utils for faster development of GraphQL tools

247 lines (246 loc) • 10.9 kB
"use strict"; 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;