@apollo/gateway
Version:
182 lines • 7.28 kB
JavaScript
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
;