UNPKG

@graphql-tools/federation

Version:

Useful tools to create and manipulate GraphQL schemas.

246 lines (245 loc) • 13.1 kB
"use strict"; var _SupergraphSchemaManager_lastSeenId, _SupergraphSchemaManager_retries, _SupergraphSchemaManager_timeout, _SupergraphSchemaManager_fetchSchema, _SupergraphSchemaManager_retryOnError, _SupergraphSchemaManager_log; Object.defineProperty(exports, "__esModule", { value: true }); exports.SupergraphSchemaManager = exports.getStitchedSchemaFromManagedFederation = exports.fetchSupergraphSdlFromManagedFederation = exports.DEFAULT_UPLINKS = void 0; const tslib_1 = require("tslib"); const fetch_1 = require("@whatwg-node/fetch"); const supergraph_js_1 = require("./supergraph.js"); const utils_js_1 = require("./utils.js"); /** * The default managed federation up links. In case of failure, you should try to cycle through these up links. * * The first one is Apollo's managed federation up link on GCP, the second one is on AWS. */ exports.DEFAULT_UPLINKS = [ 'https://uplink.api.apollographql.com/', 'https://aws.uplink.api.apollographql.com/', ]; /** * Fetches the supergraph SDL from a managed federation GraphOS up link. * @param options * @throws When the fetch fails or the response is not a valid. * @returns An object with the supergraph SDL when possible. It also includes metadata to handle polling and retry logic. * * If `lastSeenId` is provided and the supergraph has not changed, `supergraphSdl` is not present. * * If The up link report a fetch error (which is not a local fetch error), it will be returned along with polling/retry metadata. * Any local fetch error will be thrown as an exception. */ async function fetchSupergraphSdlFromManagedFederation(options = {}) { const userDefinedUplinks = process.env['APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT']?.split(',') ?? []; const { upLink = userDefinedUplinks[0] || exports.DEFAULT_UPLINKS[0], loggerByMessageLevel = DEFAULT_MESSAGE_LOGGER, fetch = fetch_1.fetch, ...variables } = options; if (!variables.graphRef) { variables.graphRef = process.env['APOLLO_GRAPH_REF']; } if (!variables.apiKey) { variables.apiKey = process.env['APOLLO_KEY']; } const response = await fetch(upLink, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: /* GraphQL */ ` query ($apiKey: String!, $graphRef: String!, $lastSeenId: ID) { routerConfig(ref: $graphRef, apiKey: $apiKey, ifAfterId: $lastSeenId) { __typename ... on FetchError { code message minDelaySeconds } ... on Unchanged { id minDelaySeconds } ... on RouterConfigResult { id supergraphSdl: supergraphSDL minDelaySeconds messages { level body } } } } `, variables, }), }); const responseBody = await response.text(); if (!response.ok) { throw new Error(`Failed to fetch supergraph SDL from managed federation up link '${upLink}': [${response.status} ${response.statusText}] ${responseBody}`); } let result; try { result = JSON.parse(responseBody); } catch (err) { throw new Error(`Failed to parse response from managed federation up link '${upLink}': ${err.message}\n\n${responseBody}`); } if (result.errors) { const errors = result.errors.map(({ message }) => '\n' + message).join(''); throw new Error(`Failed to fetch supergraph SDL from managed federation up link '${upLink}': ${errors}`); } if (!result.data?.routerConfig) { throw new Error(`Failed to fetch supergraph SDL from managed federation up link '${upLink}': ${responseBody}`); } const { routerConfig } = result.data; if (routerConfig.__typename === 'FetchError') { return { error: { code: routerConfig.code, message: routerConfig.message }, minDelaySeconds: routerConfig.minDelaySeconds, }; } if (routerConfig.__typename === 'Unchanged') { return { id: routerConfig.id, minDelaySeconds: routerConfig.minDelaySeconds }; } for (const message of routerConfig.messages) { loggerByMessageLevel[message.level](message.body); } return { supergraphSdl: routerConfig.supergraphSdl, id: routerConfig.id, minDelaySeconds: routerConfig.minDelaySeconds, }; } exports.fetchSupergraphSdlFromManagedFederation = fetchSupergraphSdlFromManagedFederation; /** * Fetches the supergraph SDL from a managed federation GraphOS up link and stitches it into an executable schema. * @param options * @throws When the fetch fails, the response is not a valid or the stitching fails. * @returns An object with the supergraph SDL and the stitched schema when possible. It also includes metadata to handle polling and retry logic. * * If `lastSeenId` is provided and the supergraph has not changed, `supergraphSdl` is not present. * * If The up link report a fetch error (which is not a local fetch error), it will be returned along with polling/retry metadata. * Any local fetch error will be thrown as an exception. */ async function getStitchedSchemaFromManagedFederation(options) { const result = await fetchSupergraphSdlFromManagedFederation({ graphRef: options.graphRef, apiKey: options.apiKey, upLink: options.upLink, lastSeenId: options.lastSeenId, fetch: options.fetch, loggerByMessageLevel: options.loggerByMessageLevel, }); if ('supergraphSdl' in result) { return { ...result, schema: (0, supergraph_js_1.getStitchedSchemaFromSupergraphSdl)({ supergraphSdl: result.supergraphSdl, onStitchingOptions: options.onStitchingOptions, httpExecutorOpts: options.httpExecutorOpts, onSubschemaConfig: options.onSubschemaConfig, batch: options.batch, }), }; } return result; } exports.getStitchedSchemaFromManagedFederation = getStitchedSchemaFromManagedFederation; const DEFAULT_MESSAGE_LOGGER = { ERROR: (message) => console.error('[Managed Federation] Uplink message: [ERROR]', message), WARN: (message) => console.warn('[Managed Federation] Uplink message: [WARN]', message), INFO: (message) => console.info('[Managed Federation] Uplink message: [INFO]', message), }; class SupergraphSchemaManager extends utils_js_1.EventEmitter { constructor(options) { super(); this.options = options; this.schema = undefined; _SupergraphSchemaManager_lastSeenId.set(this, void 0); _SupergraphSchemaManager_retries.set(this, 1); _SupergraphSchemaManager_timeout.set(this, void 0); _SupergraphSchemaManager_fetchSchema.set(this, async () => { const { retryDelaySeconds = 0, minDelaySeconds = 0 } = this.options; try { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', 'Fetch schema from managed federation'); const result = await getStitchedSchemaFromManagedFederation({ ...this.options, loggerByMessageLevel: { ERROR: message => this.emit('log', { source: 'uplink', level: 'error', message }), WARN: message => this.emit('log', { source: 'uplink', level: 'warn', message }), INFO: message => this.emit('log', { source: 'uplink', level: 'info', message }), }, lastSeenId: tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_lastSeenId, "f"), }); if ('error' in result) { tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_lastSeenId, undefined, "f"); // When an error is reported, Apollo doesn't provide an id. this.emit('error', result.error); tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_retryOnError, "f").call(this, result.error, Math.max(result.minDelaySeconds, minDelaySeconds)); return; } if ('schema' in result) { tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_lastSeenId, result.id, "f"); this.schema = result.schema; this.emit('schema', result.schema); tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', 'Supergraph successfully updated'); } else { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', 'Supergraph is up to date'); } tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_retries, 1, "f"); const delay = Math.max(result.minDelaySeconds, minDelaySeconds); tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_timeout, setTimeout(tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_fetchSchema, "f"), delay * 1000), "f"); tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', `Next pull in ${delay.toFixed(1)} seconds`); } catch (e) { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_retryOnError, "f").call(this, e, retryDelaySeconds ?? 0); this.emit('error', e); } }); _SupergraphSchemaManager_retryOnError.set(this, (error, delayInSeconds) => { var _a; const { maxRetries = 3 } = this.options; tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'error', 'Failed to pull schema from managed federation:'); if (tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_retries, "f") >= maxRetries) { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'error', 'Max retries reached, giving up'); this.emit('failure', error); return; } tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_retries, (_a = tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_retries, "f"), _a++, _a), "f"); tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', `Retrying (${tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_retries, "f")}/${maxRetries})${delayInSeconds ? ` in ${delayInSeconds.toFixed(1)} seconds` : ''}`); tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_timeout, setTimeout(tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_fetchSchema, "f"), delayInSeconds * 1000), "f"); }); _SupergraphSchemaManager_log.set(this, (level, message) => { this.emit('log', { source: 'manager', level, message }); }); registerCleanup(() => { this.stop(); }); } start() { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', 'Polling started'); tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_fetchSchema, "f").call(this); } forcePull() { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_fetchSchema, "f").call(this); tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_retries, 1, "f"); if (tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_timeout, "f")) { clearTimeout(tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_timeout, "f")); tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_timeout, undefined, "f"); } } stop() { tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_log, "f").call(this, 'info', 'Polling stopped'); if (tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_timeout, "f")) { clearTimeout(tslib_1.__classPrivateFieldGet(this, _SupergraphSchemaManager_timeout, "f")); tslib_1.__classPrivateFieldSet(this, _SupergraphSchemaManager_timeout, undefined, "f"); } } } exports.SupergraphSchemaManager = SupergraphSchemaManager; _SupergraphSchemaManager_lastSeenId = new WeakMap(), _SupergraphSchemaManager_retries = new WeakMap(), _SupergraphSchemaManager_timeout = new WeakMap(), _SupergraphSchemaManager_fetchSchema = new WeakMap(), _SupergraphSchemaManager_retryOnError = new WeakMap(), _SupergraphSchemaManager_log = new WeakMap(); function registerCleanup(cleanupFn) { if (typeof global.process === 'object') { for (const signal of ['SIGINT', 'SIGTERM', 'SIGQUIT']) process.on(signal, () => { cleanupFn(); }); } }