UNPKG

@graphql-hive/core

Version:
217 lines (216 loc) • 9.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createReporting = createReporting; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const version_js_1 = require("../version.js"); const http_client_js_1 = require("./http-client.js"); const utils_js_1 = require("./utils.js"); function createReporting(pluginOptions) { var _a, _b, _c, _d; if (!pluginOptions.reporting || pluginOptions.enabled === false) { return { async report() { }, async dispose() { }, }; } const token = pluginOptions.token; const selfHostingOptions = pluginOptions.selfHosting; const reportingOptions = pluginOptions.reporting; const logger = (0, utils_js_1.createHiveLogger)((_b = (_a = pluginOptions.agent) === null || _a === void 0 ? void 0 : _a.logger) !== null && _b !== void 0 ? _b : console, '[hive][reporting]'); (0, utils_js_1.logIf)(typeof reportingOptions.author !== 'string' || reportingOptions.author.length === 0, '[hive][reporting] author is missing', logger.error); (0, utils_js_1.logIf)(typeof reportingOptions.commit !== 'string' || reportingOptions.commit.length === 0, '[hive][reporting] commit is missing', logger.error); (0, utils_js_1.logIf)(typeof token !== 'string' || token.length === 0, '[hive][reporting] token is missing', logger.error); const endpoint = (_d = (_c = selfHostingOptions === null || selfHostingOptions === void 0 ? void 0 : selfHostingOptions.graphqlEndpoint) !== null && _c !== void 0 ? _c : reportingOptions.endpoint) !== null && _d !== void 0 ? _d : 'https://app.graphql-hive.com/graphql'; return { async report({ schema }) { var _a, _b, _c; logger.info(`Publish schema`); try { const response = await http_client_js_1.http.post(endpoint, JSON.stringify({ query, operationName: 'schemaPublish', variables: { input: { sdl: await printToSDL(schema), author: reportingOptions.author, commit: reportingOptions.commit, service: (_a = reportingOptions.serviceName) !== null && _a !== void 0 ? _a : null, url: (_b = reportingOptions.serviceUrl) !== null && _b !== void 0 ? _b : null, force: true, }, }, }), { headers: { 'graphql-client-name': 'Hive Client', 'graphql-client-version': version_js_1.version, authorization: `Bearer ${token}`, 'content-type': 'application/json', }, logger, }); if (response === null) { throw new Error('Empty response'); } const result = await response.json(); if (Array.isArray(result.errors)) { throw new Error(result.errors.map(error => error.message).join('\n')); } const data = result.data.schemaPublish; switch (data.__typename) { case 'SchemaPublishSuccess': { logger.info(`${(_c = data.successMessage) !== null && _c !== void 0 ? _c : 'Published schema'}`); return; } case 'SchemaPublishMissingServiceError': { throw new Error('Service name is not defined'); } case 'SchemaPublishMissingUrlError': { throw new Error('Service url is not defined'); } case 'SchemaPublishError': { logger.info(`Published schema (forced with ${data.errors.total} errors)`); data.errors.nodes.slice(0, 5).forEach(error => { logger.info(` - ${error.message}`); }); return; } } } catch (error) { logger.error(`Failed to report schema: ${error instanceof Error && 'message' in error ? error.message : error}`); } }, dispose() { return Promise.resolve(); }, }; } const query = (0, graphql_1.stripIgnoredCharacters)(/* GraphQL */ ` mutation schemaPublish($input: SchemaPublishInput!) { schemaPublish(input: $input) { __typename ... on SchemaPublishSuccess { initial valid successMessage: message } ... on SchemaPublishError { valid errors { nodes { message } total } } ... on SchemaPublishMissingServiceError { missingServiceError: message } ... on SchemaPublishMissingUrlError { missingUrlError: message } } } `); /** * It's a bit tricky to detect if a schema is federated or not. * For now, we just check if the schema has a _service that resolves to `_Service!` (as described in federation spec). * This may lead to issues if the schema is not a federated schema but something made by the user (I don't think we will hit that issue soon). */ function isFederatedSchema(schema) { const queryType = schema.getQueryType(); if (queryType) { const fields = queryType.getFields(); if (fields._service && fields._service.type.toString() === `_Service!`) { return true; } } return false; } const federationV2 = { scalars: new Set(['_Any', '_FieldSet']), directives: new Set([ 'key', 'requires', 'provides', 'external', 'shareable', 'extends', 'override', 'inaccessible', 'tag', ]), types: new Set(['_Service']), queryFields: new Set(['_service', '_entities']), }; /** * Extracts the SDL of a federated service from a GraphQLSchema object * We do it to not send federated schema to the registry but only the original schema provided by user */ async function extractFederationServiceSDL(schema) { const queryType = schema.getQueryType(); const serviceField = queryType.getFields()._service; const resolved = await serviceField.resolve(); if (resolved.sdl.includes('_service')) { // It seems that the schema is a federated (v2) schema. // The _service field returns the SDL of the whole subgraph, not only the sdl provided by the user. // We want to remove the federation specific types and directives from the SDL. return (0, graphql_1.print)((0, graphql_1.visit)((0, graphql_1.parse)(resolved.sdl), { ScalarTypeDefinition(node) { if (federationV2.scalars.has(node.name.value)) { return null; } return node; }, DirectiveDefinition(node) { if (federationV2.directives.has(node.name.value)) { return null; } return node; }, ObjectTypeDefinition(node) { if (federationV2.types.has(node.name.value)) { return null; } if (node.name.value === 'Query' && node.fields) { return Object.assign(Object.assign({}, node), { fields: node.fields.filter(field => !federationV2.queryFields.has(field.name.value)) }); } return node; }, })); } return resolved.sdl; } function isSchemaOfCommonNames(schema) { const queryType = schema.getQueryType(); if (queryType && queryType.name !== 'Query') { return false; } const mutationType = schema.getMutationType(); if (mutationType && mutationType.name !== 'Mutation') { return false; } const subscriptionType = schema.getSubscriptionType(); if (subscriptionType && subscriptionType.name !== 'Subscription') { return false; } return true; } function printSchemaWithDirectives(schema) { const doc = (0, utils_1.getDocumentNodeFromSchema)(schema); if (schema.description == null && isSchemaOfCommonNames(schema)) { // remove the schema definition if it's the default one // We do it to avoid sending schema definition to the registry, which may be unwanted by federated services or something return (0, graphql_1.print)({ kind: graphql_1.Kind.DOCUMENT, definitions: doc.definitions.filter(def => def.kind !== graphql_1.Kind.SCHEMA_DEFINITION), }); } return (0, graphql_1.print)(doc); } async function printToSDL(schema) { return (0, graphql_1.stripIgnoredCharacters)(isFederatedSchema(schema) ? await extractFederationServiceSDL(schema) : printSchemaWithDirectives(schema)); }