314 lines
15 KiB
JavaScript
314 lines
15 KiB
JavaScript
"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
|