requestPipeline.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  4. return new (P || (P = Promise))(function (resolve, reject) {
  5. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  7. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  8. step((generator = generator.apply(thisArg, _arguments || [])).next());
  9. });
  10. };
  11. var __importDefault = (this && this.__importDefault) || function (mod) {
  12. return (mod && mod.__esModule) ? mod : { "default": mod };
  13. };
  14. Object.defineProperty(exports, "__esModule", { value: true });
  15. exports.processGraphQLRequest = exports.APQ_CACHE_PREFIX = exports.InvalidGraphQLRequestError = void 0;
  16. const graphql_1 = require("graphql");
  17. const graphql_extensions_1 = require("graphql-extensions");
  18. const schemaInstrumentation_1 = require("./utils/schemaInstrumentation");
  19. const apollo_server_errors_1 = require("apollo-server-errors");
  20. const apollo_server_types_1 = require("apollo-server-types");
  21. Object.defineProperty(exports, "InvalidGraphQLRequestError", { enumerable: true, get: function () { return apollo_server_types_1.InvalidGraphQLRequestError; } });
  22. const dispatcher_1 = require("./utils/dispatcher");
  23. const apollo_server_caching_1 = require("apollo-server-caching");
  24. const createSHA_1 = __importDefault(require("./utils/createSHA"));
  25. const runHttpQuery_1 = require("./runHttpQuery");
  26. exports.APQ_CACHE_PREFIX = 'apq:';
  27. function computeQueryHash(query) {
  28. return createSHA_1.default('sha256')
  29. .update(query)
  30. .digest('hex');
  31. }
  32. const symbolExtensionDeprecationDone = Symbol("apolloServerExtensionDeprecationDone");
  33. function processGraphQLRequest(config, requestContext) {
  34. var _a;
  35. return __awaiter(this, void 0, void 0, function* () {
  36. const logger = requestContext.logger || console;
  37. const metrics = requestContext.metrics =
  38. requestContext.metrics || Object.create(null);
  39. const extensionStack = initializeExtensionStack();
  40. requestContext.context._extensionStack = extensionStack;
  41. const dispatcher = initializeRequestListenerDispatcher();
  42. yield initializeDataSources();
  43. const request = requestContext.request;
  44. let { query, extensions } = request;
  45. let queryHash;
  46. let persistedQueryCache;
  47. metrics.persistedQueryHit = false;
  48. metrics.persistedQueryRegister = false;
  49. if (extensions && extensions.persistedQuery) {
  50. if (!config.persistedQueries || !config.persistedQueries.cache) {
  51. return yield emitErrorAndThrow(new apollo_server_errors_1.PersistedQueryNotSupportedError());
  52. }
  53. else if (extensions.persistedQuery.version !== 1) {
  54. return yield emitErrorAndThrow(new apollo_server_types_1.InvalidGraphQLRequestError('Unsupported persisted query version'));
  55. }
  56. persistedQueryCache = config.persistedQueries.cache;
  57. if (!(persistedQueryCache instanceof apollo_server_caching_1.PrefixingKeyValueCache)) {
  58. persistedQueryCache = new apollo_server_caching_1.PrefixingKeyValueCache(persistedQueryCache, exports.APQ_CACHE_PREFIX);
  59. }
  60. queryHash = extensions.persistedQuery.sha256Hash;
  61. if (query === undefined) {
  62. query = yield persistedQueryCache.get(queryHash);
  63. if (query) {
  64. metrics.persistedQueryHit = true;
  65. }
  66. else {
  67. return yield emitErrorAndThrow(new apollo_server_errors_1.PersistedQueryNotFoundError());
  68. }
  69. }
  70. else {
  71. const computedQueryHash = computeQueryHash(query);
  72. if (queryHash !== computedQueryHash) {
  73. return yield emitErrorAndThrow(new apollo_server_types_1.InvalidGraphQLRequestError('provided sha does not match query'));
  74. }
  75. metrics.persistedQueryRegister = true;
  76. }
  77. }
  78. else if (query) {
  79. queryHash = computeQueryHash(query);
  80. }
  81. else {
  82. return yield emitErrorAndThrow(new apollo_server_types_1.InvalidGraphQLRequestError('Must provide query string.'));
  83. }
  84. requestContext.queryHash = queryHash;
  85. requestContext.source = query;
  86. yield dispatcher.invokeHookAsync('didResolveSource', requestContext);
  87. const requestDidEnd = extensionStack.requestDidStart({
  88. request: request.http,
  89. queryString: request.query,
  90. operationName: request.operationName,
  91. variables: request.variables,
  92. extensions: request.extensions,
  93. context: requestContext.context,
  94. persistedQueryHit: metrics.persistedQueryHit,
  95. persistedQueryRegister: metrics.persistedQueryRegister,
  96. requestContext: requestContext,
  97. });
  98. try {
  99. if (config.documentStore) {
  100. try {
  101. requestContext.document = yield config.documentStore.get(queryHash);
  102. }
  103. catch (err) {
  104. logger.warn('An error occurred while attempting to read from the documentStore. '
  105. + (err && err.message) || err);
  106. }
  107. }
  108. if (!requestContext.document) {
  109. const parsingDidEnd = yield dispatcher.invokeDidStartHook('parsingDidStart', requestContext);
  110. try {
  111. requestContext.document = parse(query, config.parseOptions);
  112. parsingDidEnd();
  113. }
  114. catch (syntaxError) {
  115. parsingDidEnd(syntaxError);
  116. return yield sendErrorResponse(syntaxError, apollo_server_errors_1.SyntaxError);
  117. }
  118. const validationDidEnd = yield dispatcher.invokeDidStartHook('validationDidStart', requestContext);
  119. const validationErrors = validate(requestContext.document);
  120. if (validationErrors.length === 0) {
  121. validationDidEnd();
  122. }
  123. else {
  124. validationDidEnd(validationErrors);
  125. return yield sendErrorResponse(validationErrors, apollo_server_errors_1.ValidationError);
  126. }
  127. if (config.documentStore) {
  128. Promise.resolve(config.documentStore.set(queryHash, requestContext.document)).catch(err => logger.warn('Could not store validated document. ' +
  129. (err && err.message) || err));
  130. }
  131. }
  132. const operation = graphql_1.getOperationAST(requestContext.document, request.operationName);
  133. requestContext.operation = operation || undefined;
  134. requestContext.operationName =
  135. (operation && operation.name && operation.name.value) || null;
  136. try {
  137. yield dispatcher.invokeHookAsync('didResolveOperation', requestContext);
  138. }
  139. catch (err) {
  140. if (err instanceof runHttpQuery_1.HttpQueryError) {
  141. const graphqlError = new graphql_1.GraphQLError(err.message);
  142. graphqlError.stack = err.stack;
  143. yield didEncounterErrors([graphqlError]);
  144. throw err;
  145. }
  146. return yield sendErrorResponse(err);
  147. }
  148. if (metrics.persistedQueryRegister && persistedQueryCache) {
  149. Promise.resolve(persistedQueryCache.set(queryHash, query, config.persistedQueries &&
  150. typeof config.persistedQueries.ttl !== 'undefined'
  151. ? {
  152. ttl: config.persistedQueries.ttl,
  153. }
  154. : Object.create(null))).catch(logger.warn);
  155. }
  156. let response = yield dispatcher.invokeHooksUntilNonNull('responseForOperation', requestContext);
  157. if (response == null) {
  158. const executionListeners = [];
  159. dispatcher.invokeHookSync('executionDidStart', requestContext).forEach(executionListener => {
  160. if (typeof executionListener === 'function') {
  161. executionListeners.push({
  162. executionDidEnd: executionListener,
  163. });
  164. }
  165. else if (typeof executionListener === 'object') {
  166. executionListeners.push(executionListener);
  167. }
  168. });
  169. const executionDispatcher = new dispatcher_1.Dispatcher(executionListeners);
  170. const invokeWillResolveField = (...args) => executionDispatcher.invokeDidStartHook('willResolveField', ...args);
  171. Object.defineProperty(requestContext.context, schemaInstrumentation_1.symbolExecutionDispatcherWillResolveField, { value: invokeWillResolveField });
  172. if (config.fieldResolver) {
  173. Object.defineProperty(requestContext.context, schemaInstrumentation_1.symbolUserFieldResolver, { value: config.fieldResolver });
  174. }
  175. schemaInstrumentation_1.enablePluginsForSchemaResolvers(config.schema);
  176. try {
  177. const result = yield execute(requestContext);
  178. const resultErrors = (_a = result.errors) === null || _a === void 0 ? void 0 : _a.map((e) => {
  179. var _a;
  180. if (((_a = e.nodes) === null || _a === void 0 ? void 0 : _a.length) === 1 &&
  181. e.nodes[0].kind === graphql_1.Kind.VARIABLE_DEFINITION &&
  182. e.message.startsWith(`Variable "$${e.nodes[0].variable.name.value}" got invalid value `)) {
  183. return apollo_server_errors_1.fromGraphQLError(e, {
  184. errorClass: apollo_server_errors_1.UserInputError,
  185. });
  186. }
  187. return e;
  188. });
  189. if (resultErrors) {
  190. yield didEncounterErrors(resultErrors);
  191. }
  192. response = Object.assign(Object.assign({}, result), { errors: resultErrors ? formatErrors(resultErrors) : undefined });
  193. executionDispatcher.reverseInvokeHookSync('executionDidEnd');
  194. }
  195. catch (executionError) {
  196. executionDispatcher.reverseInvokeHookSync("executionDidEnd", executionError);
  197. return yield sendErrorResponse(executionError);
  198. }
  199. }
  200. const formattedExtensions = extensionStack.format();
  201. if (Object.keys(formattedExtensions).length > 0) {
  202. response.extensions = formattedExtensions;
  203. }
  204. if (config.formatResponse) {
  205. const formattedResponse = config.formatResponse(response, requestContext);
  206. if (formattedResponse != null) {
  207. response = formattedResponse;
  208. }
  209. }
  210. return sendResponse(response);
  211. }
  212. finally {
  213. requestDidEnd();
  214. }
  215. function parse(query, parseOptions) {
  216. const parsingDidEnd = extensionStack.parsingDidStart({
  217. queryString: query,
  218. });
  219. try {
  220. return graphql_1.parse(query, parseOptions);
  221. }
  222. finally {
  223. parsingDidEnd();
  224. }
  225. }
  226. function validate(document) {
  227. let rules = graphql_1.specifiedRules;
  228. if (config.validationRules) {
  229. rules = rules.concat(config.validationRules);
  230. }
  231. const validationDidEnd = extensionStack.validationDidStart();
  232. try {
  233. return graphql_1.validate(config.schema, document, rules);
  234. }
  235. finally {
  236. validationDidEnd();
  237. }
  238. }
  239. function execute(requestContext) {
  240. return __awaiter(this, void 0, void 0, function* () {
  241. const { request, document } = requestContext;
  242. const executionArgs = {
  243. schema: config.schema,
  244. document,
  245. rootValue: typeof config.rootValue === 'function'
  246. ? config.rootValue(document)
  247. : config.rootValue,
  248. contextValue: requestContext.context,
  249. variableValues: request.variables,
  250. operationName: request.operationName,
  251. fieldResolver: config.fieldResolver,
  252. };
  253. const executionDidEnd = extensionStack.executionDidStart({
  254. executionArgs,
  255. });
  256. try {
  257. if (config.executor) {
  258. return yield config.executor(requestContext);
  259. }
  260. else {
  261. return yield graphql_1.execute(executionArgs);
  262. }
  263. }
  264. finally {
  265. executionDidEnd();
  266. }
  267. });
  268. }
  269. function sendResponse(response) {
  270. return __awaiter(this, void 0, void 0, function* () {
  271. requestContext.response = extensionStack.willSendResponse({
  272. graphqlResponse: Object.assign(Object.assign({}, requestContext.response), { errors: response.errors, data: response.data, extensions: response.extensions }),
  273. context: requestContext.context,
  274. }).graphqlResponse;
  275. yield dispatcher.invokeHookAsync('willSendResponse', requestContext);
  276. return requestContext.response;
  277. });
  278. }
  279. function emitErrorAndThrow(error) {
  280. return __awaiter(this, void 0, void 0, function* () {
  281. yield didEncounterErrors([error]);
  282. throw error;
  283. });
  284. }
  285. function didEncounterErrors(errors) {
  286. return __awaiter(this, void 0, void 0, function* () {
  287. requestContext.errors = errors;
  288. extensionStack.didEncounterErrors(errors);
  289. return yield dispatcher.invokeHookAsync('didEncounterErrors', requestContext);
  290. });
  291. }
  292. function sendErrorResponse(errorOrErrors, errorClass) {
  293. return __awaiter(this, void 0, void 0, function* () {
  294. const errors = Array.isArray(errorOrErrors)
  295. ? errorOrErrors
  296. : [errorOrErrors];
  297. yield didEncounterErrors(errors);
  298. return sendResponse({
  299. errors: formatErrors(errors.map(err => apollo_server_errors_1.fromGraphQLError(err, errorClass && {
  300. errorClass,
  301. }))),
  302. });
  303. });
  304. }
  305. function formatErrors(errors) {
  306. return apollo_server_errors_1.formatApolloErrors(errors, {
  307. formatter: config.formatError,
  308. debug: requestContext.debug,
  309. });
  310. }
  311. function initializeRequestListenerDispatcher() {
  312. const requestListeners = [];
  313. if (config.plugins) {
  314. for (const plugin of config.plugins) {
  315. if (!plugin.requestDidStart)
  316. continue;
  317. const listener = plugin.requestDidStart(requestContext);
  318. if (listener) {
  319. requestListeners.push(listener);
  320. }
  321. }
  322. }
  323. return new dispatcher_1.Dispatcher(requestListeners);
  324. }
  325. function initializeExtensionStack() {
  326. var _a;
  327. if ((_a = config.extensions) === null || _a === void 0 ? void 0 : _a.length) {
  328. graphql_extensions_1.enableGraphQLExtensions(config.schema);
  329. }
  330. const extensions = config.extensions ? config.extensions.map(f => f()) : [];
  331. const hasOwn = Object.prototype.hasOwnProperty;
  332. extensions.forEach((extension) => {
  333. if (!extension.constructor ||
  334. hasOwn.call(extension.constructor, symbolExtensionDeprecationDone)) {
  335. return;
  336. }
  337. Object.defineProperty(extension.constructor, symbolExtensionDeprecationDone, { value: true });
  338. const extensionName = extension.constructor.name;
  339. logger.warn('[deprecated] ' +
  340. (extensionName
  341. ? 'A "' + extensionName + '" '
  342. : 'An anonymous extension ') +
  343. 'was defined within the "extensions" configuration for ' +
  344. 'Apollo Server. The API on which this extension is built ' +
  345. '("graphql-extensions") is being deprecated in the next major ' +
  346. 'version of Apollo Server in favor of the new plugin API. See ' +
  347. 'https://go.apollo.dev/s/plugins for the documentation on how ' +
  348. 'these plugins are to be defined and used.');
  349. });
  350. return new graphql_extensions_1.GraphQLExtensionStack(extensions);
  351. }
  352. function initializeDataSources() {
  353. return __awaiter(this, void 0, void 0, function* () {
  354. if (config.dataSources) {
  355. const context = requestContext.context;
  356. const dataSources = config.dataSources();
  357. const initializers = [];
  358. for (const dataSource of Object.values(dataSources)) {
  359. if (dataSource.initialize) {
  360. initializers.push(dataSource.initialize({
  361. context,
  362. cache: requestContext.cache,
  363. }));
  364. }
  365. }
  366. yield Promise.all(initializers);
  367. if ('dataSources' in context) {
  368. throw new Error('Please use the dataSources config option instead of putting dataSources on the context yourself.');
  369. }
  370. context.dataSources = dataSources;
  371. }
  372. });
  373. }
  374. });
  375. }
  376. exports.processGraphQLRequest = processGraphQLRequest;
  377. //# sourceMappingURL=requestPipeline.js.map