"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processGraphQLRequest = exports.APQ_CACHE_PREFIX = void 0; const utils_createhash_1 = require("@apollo/utils.createhash"); const graphql_1 = require("graphql"); const schemaInstrumentation_js_1 = require("./utils/schemaInstrumentation.js"); const internalErrorClasses_js_1 = require("./internalErrorClasses.js"); const errorNormalize_js_1 = require("./errorNormalize.js"); const invokeHooks_js_1 = require("./utils/invokeHooks.js"); const makeGatewayGraphQLRequestContext_js_1 = require("./utils/makeGatewayGraphQLRequestContext.js"); const runHttpQuery_js_1 = require("./runHttpQuery.js"); const isDefined_js_1 = require("./utils/isDefined.js"); const incrementalDeliveryPolyfill_js_1 = require("./incrementalDeliveryPolyfill.js"); const HeaderMap_js_1 = require("./utils/HeaderMap.js"); exports.APQ_CACHE_PREFIX = 'apq:'; function computeQueryHash(query) { return (0, utils_createhash_1.createHash)('sha256').update(query).digest('hex'); } function isBadUserInputGraphQLError(error) { return (error.nodes?.length === 1 && error.nodes[0].kind === graphql_1.Kind.VARIABLE_DEFINITION && (error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" got invalid value `) || error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" of required type `) || error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" of non-null type `))); } async function processGraphQLRequest(schemaDerivedData, server, internals, requestContext) { const requestListeners = (await Promise.all(internals.plugins.map((p) => p.requestDidStart?.(requestContext)))).filter(isDefined_js_1.isDefined); const request = requestContext.request; let { query, extensions } = request; let queryHash; requestContext.metrics.persistedQueryHit = false; requestContext.metrics.persistedQueryRegister = false; if (extensions?.persistedQuery) { if (!internals.persistedQueries) { return await sendErrorResponse([new internalErrorClasses_js_1.PersistedQueryNotSupportedError()]); } else if (extensions.persistedQuery.version !== 1) { return await sendErrorResponse([ new graphql_1.GraphQLError('Unsupported persisted query version', { extensions: { http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(400) }, }), ]); } queryHash = extensions.persistedQuery.sha256Hash; if (query === undefined) { query = await internals.persistedQueries.cache.get(queryHash); if (query) { requestContext.metrics.persistedQueryHit = true; } else { return await sendErrorResponse([new internalErrorClasses_js_1.PersistedQueryNotFoundError()]); } } else { const computedQueryHash = computeQueryHash(query); if (queryHash !== computedQueryHash) { return await sendErrorResponse([ new graphql_1.GraphQLError('provided sha does not match query', { extensions: { http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(400) }, }), ]); } requestContext.metrics.persistedQueryRegister = true; } } else if (query) { queryHash = computeQueryHash(query); } else { return await sendErrorResponse([ new internalErrorClasses_js_1.BadRequestError('GraphQL operations must contain a non-empty `query` or a `persistedQuery` extension.'), ]); } requestContext.queryHash = queryHash; requestContext.source = query; await Promise.all(requestListeners.map((l) => l.didResolveSource?.(requestContext))); if (schemaDerivedData.documentStore) { try { requestContext.document = await schemaDerivedData.documentStore.get(schemaDerivedData.documentStoreKeyPrefix + queryHash); } catch (err) { server.logger.warn('An error occurred while attempting to read from the documentStore. ' + (0, errorNormalize_js_1.ensureError)(err).message); } } if (!requestContext.document) { const parsingDidEnd = await (0, invokeHooks_js_1.invokeDidStartHook)(requestListeners, async (l) => l.parsingDidStart?.(requestContext)); try { requestContext.document = (0, graphql_1.parse)(query, internals.parseOptions); } catch (syntaxMaybeError) { const error = (0, errorNormalize_js_1.ensureError)(syntaxMaybeError); await parsingDidEnd(error); return await sendErrorResponse([ new internalErrorClasses_js_1.SyntaxError((0, errorNormalize_js_1.ensureGraphQLError)(error)), ]); } await parsingDidEnd(); if (internals.dangerouslyDisableValidation !== true) { const validationDidEnd = await (0, invokeHooks_js_1.invokeDidStartHook)(requestListeners, async (l) => l.validationDidStart?.(requestContext)); const validationErrors = (0, graphql_1.validate)(schemaDerivedData.schema, requestContext.document, [...graphql_1.specifiedRules, ...internals.validationRules]); if (validationErrors.length === 0) { await validationDidEnd(); } else { await validationDidEnd(validationErrors); return await sendErrorResponse(validationErrors.map((error) => new internalErrorClasses_js_1.ValidationError(error))); } } if (schemaDerivedData.documentStore) { Promise.resolve(schemaDerivedData.documentStore.set(schemaDerivedData.documentStoreKeyPrefix + queryHash, requestContext.document)).catch((err) => server.logger.warn('Could not store validated document. ' + err?.message || err)); } } const operation = (0, graphql_1.getOperationAST)(requestContext.document, request.operationName); requestContext.operation = operation || undefined; requestContext.operationName = operation?.name?.value || null; if (request.http?.method === 'GET' && operation?.operation && operation.operation !== 'query') { return await sendErrorResponse([ new internalErrorClasses_js_1.BadRequestError(`GET requests only support query operations, not ${operation.operation} operations`, { extensions: { http: { status: 405, headers: new HeaderMap_js_1.HeaderMap([['allow', 'POST']]) }, }, }), ]); } try { await Promise.all(requestListeners.map((l) => l.didResolveOperation?.(requestContext))); } catch (err) { return await sendErrorResponse([(0, errorNormalize_js_1.ensureGraphQLError)(err)]); } if (requestContext.metrics.persistedQueryRegister && internals.persistedQueries) { const ttl = internals.persistedQueries?.ttl; Promise.resolve(internals.persistedQueries.cache.set(queryHash, query, ttl !== undefined ? { ttl: internals.persistedQueries?.ttl } : undefined)).catch(server.logger.warn); } const responseFromPlugin = await (0, invokeHooks_js_1.invokeHooksUntilDefinedAndNonNull)(requestListeners, async (l) => await l.responseForOperation?.(requestContext)); if (responseFromPlugin !== null) { requestContext.response.body = responseFromPlugin.body; (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, responseFromPlugin.http); } else { const executionListeners = (await Promise.all(requestListeners.map((l) => l.executionDidStart?.(requestContext)))).filter(isDefined_js_1.isDefined); executionListeners.reverse(); if (executionListeners.some((l) => l.willResolveField)) { const invokeWillResolveField = (...args) => (0, invokeHooks_js_1.invokeSyncDidStartHook)(executionListeners, (l) => l.willResolveField?.(...args)); Object.defineProperty(requestContext.contextValue, schemaInstrumentation_js_1.symbolExecutionDispatcherWillResolveField, { value: invokeWillResolveField }); if (internals.fieldResolver) { Object.defineProperty(requestContext.contextValue, schemaInstrumentation_js_1.symbolUserFieldResolver, { value: internals.fieldResolver, }); } (0, schemaInstrumentation_js_1.enablePluginsForSchemaResolvers)(schemaDerivedData.schema); } try { const fullResult = await execute(requestContext); const result = 'singleResult' in fullResult ? fullResult.singleResult : fullResult.initialResult; if (!requestContext.operation) { if (!result.errors?.length) { throw new Error('Unexpected error: Apollo Server did not resolve an operation but execute did not return errors'); } throw new internalErrorClasses_js_1.OperationResolutionError(result.errors[0]); } const resultErrors = result.errors?.map((e) => { if (isBadUserInputGraphQLError(e) && e.extensions?.code == null) { return new internalErrorClasses_js_1.UserInputError(e); } return e; }); if (resultErrors) { await didEncounterErrors(resultErrors); } const { formattedErrors, httpFromErrors } = resultErrors ? formatErrors(resultErrors) : { formattedErrors: undefined, httpFromErrors: (0, runHttpQuery_js_1.newHTTPGraphQLHead)() }; if (internals.status400ForVariableCoercionErrors && resultErrors?.length && result.data === undefined && !httpFromErrors.status) { httpFromErrors.status = 400; } (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, httpFromErrors); if ('singleResult' in fullResult) { requestContext.response.body = { kind: 'single', singleResult: { ...result, errors: formattedErrors, }, }; } else { requestContext.response.body = { kind: 'incremental', initialResult: { ...fullResult.initialResult, errors: formattedErrors, }, subsequentResults: fullResult.subsequentResults, }; } } catch (executionMaybeError) { const executionError = (0, errorNormalize_js_1.ensureError)(executionMaybeError); await Promise.all(executionListeners.map((l) => l.executionDidEnd?.(executionError))); return await sendErrorResponse([(0, errorNormalize_js_1.ensureGraphQLError)(executionError)]); } await Promise.all(executionListeners.map((l) => l.executionDidEnd?.())); } await invokeWillSendResponse(); if (!requestContext.response.body) { throw Error('got to end of processGraphQLRequest without setting body?'); } return requestContext.response; async function execute(requestContext) { const { request, document } = requestContext; if (internals.__testing_incrementalExecutionResults) { return internals.__testing_incrementalExecutionResults; } else if (internals.gatewayExecutor) { const result = await internals.gatewayExecutor((0, makeGatewayGraphQLRequestContext_js_1.makeGatewayGraphQLRequestContext)(requestContext, server, internals)); return { singleResult: result }; } else { const resultOrResults = await (0, incrementalDeliveryPolyfill_js_1.executeIncrementally)({ schema: schemaDerivedData.schema, document, rootValue: typeof internals.rootValue === 'function' ? internals.rootValue(document) : internals.rootValue, contextValue: requestContext.contextValue, variableValues: request.variables, operationName: request.operationName, fieldResolver: internals.fieldResolver, }); if ('initialResult' in resultOrResults) { return { initialResult: resultOrResults.initialResult, subsequentResults: formatErrorsInSubsequentResults(resultOrResults.subsequentResults), }; } else { return { singleResult: resultOrResults }; } } } async function* formatErrorsInSubsequentResults(results) { for await (const result of results) { const payload = result.incremental ? { ...result, incremental: await seriesAsyncMap(result.incremental, async (incrementalResult) => { const { errors } = incrementalResult; if (errors) { await Promise.all(requestListeners.map((l) => l.didEncounterSubsequentErrors?.(requestContext, errors))); return { ...incrementalResult, errors: formatErrors(errors).formattedErrors, }; } return incrementalResult; }), } : result; await Promise.all(requestListeners.map((l) => l.willSendSubsequentPayload?.(requestContext, payload))); yield payload; } } async function invokeWillSendResponse() { await Promise.all(requestListeners.map((l) => l.willSendResponse?.(requestContext))); } async function didEncounterErrors(errors) { requestContext.errors = errors; return await Promise.all(requestListeners.map((l) => l.didEncounterErrors?.(requestContext))); } async function sendErrorResponse(errors) { await didEncounterErrors(errors); const { formattedErrors, httpFromErrors } = formatErrors(errors); requestContext.response.body = { kind: 'single', singleResult: { errors: formattedErrors, }, }; (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, httpFromErrors); if (!requestContext.response.http.status) { requestContext.response.http.status = 500; } await invokeWillSendResponse(); return requestContext.response; } function formatErrors(errors) { return (0, errorNormalize_js_1.normalizeAndFormatErrors)(errors, { formatError: internals.formatError, includeStacktraceInErrorResponses: internals.includeStacktraceInErrorResponses, }); } } exports.processGraphQLRequest = processGraphQLRequest; async function seriesAsyncMap(ts, fn) { const us = []; for (const t of ts) { const u = await fn(t); us.push(u); } return us; } //# sourceMappingURL=requestPipeline.js.map