UNPKG

@graphql-hive/yoga

Version:
284 lines (283 loc) • 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSupergraphSDLFetcher = exports.createServicesFetcher = exports.createSchemaFetcher = exports.atLeastOnceSampler = void 0; exports.createHive = createHive; exports.useHive = useHive; const graphql_1 = require("graphql"); const graphql_yoga_1 = require("graphql-yoga"); const core_1 = require("@graphql-hive/core"); const logger_1 = require("@graphql-hive/logger"); const plugin_persisted_operations_1 = require("@graphql-yoga/plugin-persisted-operations"); const version_js_1 = require("./version.js"); var core_2 = require("@graphql-hive/core"); Object.defineProperty(exports, "atLeastOnceSampler", { enumerable: true, get: function () { return core_2.atLeastOnceSampler; } }); Object.defineProperty(exports, "createSchemaFetcher", { enumerable: true, get: function () { return core_2.createSchemaFetcher; } }); Object.defineProperty(exports, "createServicesFetcher", { enumerable: true, get: function () { return core_2.createServicesFetcher; } }); Object.defineProperty(exports, "createSupergraphSDLFetcher", { enumerable: true, get: function () { return core_2.createSupergraphSDLFetcher; } }); function createHive(clientOrOptions) { return (0, core_1.createHive)(Object.assign(Object.assign({}, clientOrOptions), { agent: Object.assign({ name: 'hive-client-yoga', version: version_js_1.version }, clientOrOptions.agent) })); } function useHive(clientOrOptions) { var _a, _b; const parsedDocumentCache = (0, graphql_yoga_1._createLRUCache)(); let latestSchema = null; const contextualCache = new WeakMap(); let hive; let yoga; let onYogaInit; let onYogaInitDefered = new Promise(res => (onYogaInit = () => { res(); onYogaInitDefered = null; })); const fieldLevelMetricsEnabled = (0, core_1.isHiveClient)(clientOrOptions) ? false : (typeof clientOrOptions.usage === 'object' && ((_a = clientOrOptions.usage) === null || _a === void 0 ? void 0 : _a.fieldLevelMetricsEnabled)) || false; const operationCache = fieldLevelMetricsEnabled ? (0, graphql_yoga_1._createLRUCache)({ max: (0, core_1.isHiveClient)(clientOrOptions) ? 10000 : ((_b = clientOrOptions.cache) !== null && _b !== void 0 ? _b : 10000), }) : null; return { onYogaInit(payload) { yoga = payload.yoga; onYogaInit(); }, onSchemaChange({ schema }) { hive.reportSchema({ schema }); latestSchema = schema; operationCache === null || operationCache === void 0 ? void 0 : operationCache.clear(); }, onParams(context) { // we set the params if there is either a query or documentId in the request if ((context.params.query || 'documentId' in context.params) && latestSchema) { contextualCache.set(context.context, { callback: hive.collectUsage(), paramsArgs: context.params, }); } }, // since response-cache modifies the executed GraphQL document, we need to extract it after parsing. onParse(parseCtx) { return ctx => { const result = ctx.result; if (result.kind === graphql_1.Kind.DOCUMENT) { const record = contextualCache.get(ctx.context); if (record) { // set the documents on thee operation context to be used in other callbacks record.parsedDocument = result; parsedDocumentCache.set(parseCtx.params.source, result); } if (fieldLevelMetricsEnabled && operationCache) { // We need __typename on every object in the result so we can // resolve abstract types (unions/interfaces) to concrete type coordinates // when recording field-level metrics downstream. // This is done here for more performant caching of the result. const query = parseCtx.params.source; const cachedDocument = operationCache.get(query); if (cachedDocument) { // If "true" is cached, then this operation doesn't need stored because it's identical to the original. // Else, the document hash been modified and cached if (cachedDocument !== true) { parseCtx.setParsedDocument(cachedDocument); } } else if (latestSchema) { const modifiedDocument = (0, core_1.addHiveTypenames)(ctx.result, latestSchema); operationCache.set(query, result === modifiedDocument || modifiedDocument); if (result !== modifiedDocument) { parseCtx.setParsedDocument(modifiedDocument); } } } } }; }, onExecute() { return { onExecuteDone({ args, result }) { var _a; const record = contextualCache.get(args.contextValue); if (!record) { return; } record.executionArgs = args; if (!(0, core_1.isAsyncIterable)(result)) { if (result.data && fieldLevelMetricsEnabled) { (0, core_1.hideInjectedTypenames)(result.data); } args.contextValue.waitUntil(record.callback.finish(Object.assign(Object.assign({}, record.executionArgs), { // pass the original parsed document to the callback so the operation name and structure match the original document: (_a = record.parsedDocument) !== null && _a !== void 0 ? _a : record.executionArgs.document }), result, record.documentId)); return; } const errors = []; return { onNext(ctx) { if (ctx.result.data && fieldLevelMetricsEnabled) { (0, core_1.hideInjectedTypenames)(ctx.result.data); } if (!ctx.result.errors) { return; } errors.push(...ctx.result.errors); }, onEnd() { var _a, _b, _c; args.contextValue.waitUntil(record.callback.finish(Object.assign(Object.assign({}, args), { // pass the original parsed document to the callback so the operation name and structure match the original document: (_c = (_a = record.parsedDocument) !== null && _a !== void 0 ? _a : (_b = record.executionArgs) === null || _b === void 0 ? void 0 : _b.document) !== null && _c !== void 0 ? _c : args.document }), errors.length ? { errors } : {}, record.documentId)); }, }; }, }; }, onSubscribe(context) { // In GraphQL.js 16 `onSubscribe` is called even if the schema does not contain a subscription type. // In GraphQL.js 17 there is a new validation rule [`KnownOperationTypes`](https://github.com/graphql/graphql-js/pull/3601) // that prevents `onSubscribe` being called if the schema has no subscription type. // We want to avoid running SDK code here as it would raise an exception in the schema coordinate collection. if (!context.args.schema.getSubscriptionType()) { return; } const record = contextualCache.get(context.args.contextValue); return { onSubscribeResult() { var _a, _b, _c; const persistedDocumentId = record === null || record === void 0 ? void 0 : record.documentId; hive.collectSubscriptionUsage({ args: Object.assign(Object.assign(Object.assign({}, context.args), record === null || record === void 0 ? void 0 : record.executionArgs), { document: (_c = (_a = record === null || record === void 0 ? void 0 : record.parsedDocument) !== null && _a !== void 0 ? _a : (_b = record === null || record === void 0 ? void 0 : record.executionArgs) === null || _b === void 0 ? void 0 : _b.document) !== null && _c !== void 0 ? _c : context.args.document }), experimental__persistedDocumentHash: persistedDocumentId, }); }, }; }, onResultProcess({ serverContext, result }) { const record = contextualCache.get(serverContext); if (!record || Array.isArray(result) || (0, core_1.isAsyncIterable)(result) || record.executionArgs) { return; } // Report if execution was skipped due to response cache ( Symbol.for('servedFromResponseCache') in context.result) if (record.paramsArgs.query && latestSchema && Symbol.for('servedFromResponseCache') in result) { try { let document = parsedDocumentCache.get(record.paramsArgs.query); if (document === undefined) { document = (0, graphql_1.parse)(record.paramsArgs.query); parsedDocumentCache.set(record.paramsArgs.query, document); } serverContext.waitUntil(record.callback.finish({ document, schema: latestSchema, variableValues: record.paramsArgs.variables, operationName: record.paramsArgs.operationName, contextValue: serverContext, }, result, record.documentId)); } catch (err) { yoga.logger.error(err); } } }, onPluginInit({ addPlugin }) { var _a; hive = (0, core_1.isHiveClient)(clientOrOptions) ? clientOrOptions : createHive(Object.assign(Object.assign({}, clientOrOptions), { logger: (_a = clientOrOptions.logger) !== null && _a !== void 0 ? _a : new logger_1.Logger({ writers: [ { write(level, attrs, msg) { level = level === 'trace' ? 'debug' : level; if (!onYogaInitDefered) { yoga.logger[level](msg, attrs); return; } // Defer logs until yoga instance is initialized // Ideally, onPluginInit would provide us access to the logger instance // See https://github.com/graphql-hive/graphql-yoga/issues/4048#issuecomment-3576258603 void onYogaInitDefered.then(() => { yoga === null || yoga === void 0 ? void 0 : yoga.logger[level](msg, attrs); }); }, }, ], }), agent: clientOrOptions.agent ? Object.assign({ // Hive Plugin should respect the given FetchAPI, note that this is not `yoga.fetch` fetch: (...args) => yoga.fetchAPI.fetch(...args) }, clientOrOptions.agent) : undefined })); void hive.info(); const persistedDocuments = hive.persistedDocuments; if (persistedDocuments) { addPlugin((0, plugin_persisted_operations_1.usePersistedOperations)({ extractPersistedOperationId(body, request) { if ('documentId' in body && typeof body.documentId === 'string') { return body.documentId; } const documentId = new URL(request.url).searchParams.get('documentId'); if (documentId) { return documentId; } return null; }, async getPersistedOperation(key, _request, context) { let document; try { document = await persistedDocuments.resolve(key, { waitUntil: context.waitUntil, }); } catch (error) { if (error && typeof error === 'object' && 'code' in error && error.code === 'INVALID_DOCUMENT_ID' && 'message' in error && typeof error.message === 'string') { throw new graphql_1.GraphQLError(error.message, { extensions: { code: 'INVALID_DOCUMENT_ID' }, }); } throw error; } // after we resolve the document we need to update the cache record to contain the resolved document if (document) { const record = contextualCache.get(context); if (record) { record.documentId = key; record.paramsArgs = Object.assign(Object.assign({}, record.paramsArgs), { query: document }); } } return document; }, allowArbitraryOperations(request) { return persistedDocuments.allowArbitraryDocuments(request); }, customErrors: { keyNotFound() { return new graphql_1.GraphQLError('Persisted document not found.', { extensions: { code: 'PERSISTED_DOCUMENT_NOT_FOUND' }, }); }, notFound() { return new graphql_1.GraphQLError('Persisted document not found.', { extensions: { code: 'PERSISTED_DOCUMENT_NOT_FOUND' }, }); }, persistedQueryOnly() { return new graphql_1.GraphQLError('No persisted document provided.', { extensions: { code: 'PERSISTED_DOCUMENT_REQUIRED' }, }); }, }, })); } }, onDispose() { if (hive[core_1.autoDisposeSymbol]) { return hive.dispose(); } }, }; }