UNPKG

@apollo/gateway

Version:
182 lines 7.28 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RemoteGraphQLDataSource = void 0; const predicates_1 = require("../utilities/predicates"); const types_1 = require("./types"); const utils_createhash_1 = require("@apollo/utils.createhash"); const parseCacheControlHeader_1 = require("./parseCacheControlHeader"); const make_fetch_happen_1 = __importDefault(require("make-fetch-happen")); const node_fetch_1 = require("node-fetch"); const graphql_1 = require("graphql"); class RemoteGraphQLDataSource { constructor(config) { this.apq = false; this.honorSubgraphCacheControlHeader = true; this.fetcher = make_fetch_happen_1.default.defaults({ maxSockets: Infinity, retry: false, }); if (config) { return Object.assign(this, config); } } async process(options) { var _a; const { request, context: originalContext } = options; const pathInIncomingRequest = options.kind === types_1.GraphQLDataSourceRequestKind.INCOMING_OPERATION ? options.pathInIncomingRequest : undefined; const context = originalContext; const headers = new node_fetch_1.Headers(); if ((_a = request.http) === null || _a === void 0 ? void 0 : _a.headers) { for (const [name, value] of request.http.headers) { headers.append(name, value); } } headers.set('Content-Type', 'application/json'); request.http = { method: 'POST', url: this.url, headers, }; if (this.willSendRequest) { await this.willSendRequest(options); } if (!request.query) { throw new Error('Missing query'); } const { query, ...requestWithoutQuery } = request; const overallCachePolicy = this.honorSubgraphCacheControlHeader && options.kind === types_1.GraphQLDataSourceRequestKind.INCOMING_OPERATION && options.incomingRequestContext.overallCachePolicy && 'restrict' in options.incomingRequestContext.overallCachePolicy ? options.incomingRequestContext.overallCachePolicy : null; if (this.apq) { const apqHash = (0, utils_createhash_1.createHash)('sha256').update(request.query).digest('hex'); requestWithoutQuery.extensions = { ...request.extensions, persistedQuery: { version: 1, sha256Hash: apqHash, }, }; const apqOptimisticResponse = await this.sendRequest(requestWithoutQuery, context); if (!apqOptimisticResponse.errors || !apqOptimisticResponse.errors.find((error) => error.message === 'PersistedQueryNotFound')) { return this.respond({ response: apqOptimisticResponse, request: requestWithoutQuery, context, overallCachePolicy, pathInIncomingRequest }); } } const requestWithQuery = { query, ...requestWithoutQuery, }; const response = await this.sendRequest(requestWithQuery, context); return this.respond({ response, request: requestWithQuery, context, overallCachePolicy, pathInIncomingRequest }); } async sendRequest(request, context) { if (!request.http) { throw new Error("Internal error: Only 'http' requests are supported."); } const { http, ...requestWithoutHttp } = request; const stringifiedRequestWithoutHttp = JSON.stringify(requestWithoutHttp); const requestInit = { method: http.method, headers: Object.fromEntries(http.headers), body: stringifiedRequestWithoutHttp, }; const fetchRequest = new node_fetch_1.Request(http.url, requestInit); let fetchResponse; try { fetchResponse = await this.fetcher(http.url, requestInit); if (!fetchResponse.ok) { throw await this.errorFromResponse(fetchResponse); } const body = await this.parseBody(fetchResponse, fetchRequest, context); if (!(0, predicates_1.isObject)(body)) { throw new Error(`Expected JSON response body, but received: ${body}`); } return { ...body, http: fetchResponse, }; } catch (error) { this.didEncounterError(error, fetchRequest, fetchResponse, context, request); throw error; } } async respond({ response, request, context, overallCachePolicy, pathInIncomingRequest }) { var _a; const processedResponse = typeof this.didReceiveResponse === 'function' ? await this.didReceiveResponse({ response, request, context, pathInIncomingRequest }) : response; if (overallCachePolicy) { const parsed = (0, parseCacheControlHeader_1.parseCacheControlHeader)((_a = response.http) === null || _a === void 0 ? void 0 : _a.headers.get('cache-control')); const hint = { maxAge: 0 }; const maxAge = parsed['max-age']; if (typeof maxAge === 'string' && maxAge.match(/^[0-9]+$/)) { hint.maxAge = +maxAge; } if (parsed['private'] === true) { hint.scope = 'PRIVATE'; } if (parsed['public'] === true) { hint.scope = 'PUBLIC'; } overallCachePolicy.restrict(hint); } return processedResponse; } didEncounterError(error, _fetchRequest, _fetchResponse, _context, _request) { throw error; } parseBody(fetchResponse, _fetchRequest, _context) { const contentType = fetchResponse.headers.get('Content-Type'); if (contentType && (contentType.startsWith('application/json') || contentType.startsWith('application/graphql-response+json'))) { return fetchResponse.json(); } else { return fetchResponse.text(); } } async errorFromResponse(response) { const body = await this.parseBody(response); const extensions = { response: { url: response.url, status: response.status, statusText: response.statusText, body, }, }; if (response.status === 401) { extensions.code = 'UNAUTHENTICATED'; } else if (response.status === 403) { extensions.code = 'FORBIDDEN'; } return new graphql_1.GraphQLError(`${response.status}: ${response.statusText}`, { extensions, }); } } exports.RemoteGraphQLDataSource = RemoteGraphQLDataSource; //# sourceMappingURL=RemoteGraphQLDataSource.js.map