"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApolloServerPluginCacheControl = void 0; const graphql_1 = require("graphql"); const cachePolicy_js_1 = require("../../cachePolicy.js"); const internalPlugin_js_1 = require("../../internalPlugin.js"); const lru_cache_1 = __importDefault(require("lru-cache")); function ApolloServerPluginCacheControl(options = Object.create(null)) { let typeAnnotationCache; let fieldAnnotationCache; return (0, internalPlugin_js_1.internalPlugin)({ __internal_plugin_id__: 'CacheControl', __is_disabled_plugin__: false, async serverWillStart({ schema }) { typeAnnotationCache = new lru_cache_1.default({ max: Object.values(schema.getTypeMap()).filter(graphql_1.isCompositeType) .length, }); fieldAnnotationCache = new lru_cache_1.default({ max: Object.values(schema.getTypeMap()) .filter(graphql_1.isObjectType) .flatMap((t) => Object.values(t.getFields())).length + Object.values(schema.getTypeMap()) .filter(graphql_1.isInterfaceType) .flatMap((t) => Object.values(t.getFields())).length, }); return undefined; }, async requestDidStart(requestContext) { function memoizedCacheAnnotationFromType(t) { const existing = typeAnnotationCache.get(t); if (existing) { return existing; } const annotation = cacheAnnotationFromType(t); typeAnnotationCache.set(t, annotation); return annotation; } function memoizedCacheAnnotationFromField(field) { const existing = fieldAnnotationCache.get(field); if (existing) { return existing; } const annotation = cacheAnnotationFromField(field); fieldAnnotationCache.set(field, annotation); return annotation; } const defaultMaxAge = options.defaultMaxAge ?? 0; const calculateHttpHeaders = options.calculateHttpHeaders ?? true; const { __testing__cacheHints } = options; return { async executionDidStart() { if (isRestricted(requestContext.overallCachePolicy)) { const fakeFieldPolicy = (0, cachePolicy_js_1.newCachePolicy)(); return { willResolveField({ info }) { info.cacheControl = { setCacheHint: (dynamicHint) => { fakeFieldPolicy.replace(dynamicHint); }, cacheHint: fakeFieldPolicy, cacheHintFromType: memoizedCacheAnnotationFromType, }; }, }; } return { willResolveField({ info }) { const fieldPolicy = (0, cachePolicy_js_1.newCachePolicy)(); let inheritMaxAge = false; const targetType = (0, graphql_1.getNamedType)(info.returnType); if ((0, graphql_1.isCompositeType)(targetType)) { const typeAnnotation = memoizedCacheAnnotationFromType(targetType); fieldPolicy.replace(typeAnnotation); inheritMaxAge = !!typeAnnotation.inheritMaxAge; } const fieldAnnotation = memoizedCacheAnnotationFromField(info.parentType.getFields()[info.fieldName]); if (fieldAnnotation.inheritMaxAge && fieldPolicy.maxAge === undefined) { inheritMaxAge = true; if (fieldAnnotation.scope) { fieldPolicy.replace({ scope: fieldAnnotation.scope }); } } else { fieldPolicy.replace(fieldAnnotation); } info.cacheControl = { setCacheHint: (dynamicHint) => { fieldPolicy.replace(dynamicHint); }, cacheHint: fieldPolicy, cacheHintFromType: memoizedCacheAnnotationFromType, }; return () => { if (fieldPolicy.maxAge === undefined && (((0, graphql_1.isCompositeType)(targetType) && !inheritMaxAge) || !info.path.prev)) { fieldPolicy.restrict({ maxAge: defaultMaxAge }); } if (__testing__cacheHints && isRestricted(fieldPolicy)) { const path = (0, graphql_1.responsePathAsArray)(info.path).join('.'); if (__testing__cacheHints.has(path)) { throw Error("shouldn't happen: addHint should only be called once per path"); } __testing__cacheHints.set(path, { maxAge: fieldPolicy.maxAge, scope: fieldPolicy.scope, }); } requestContext.overallCachePolicy.restrict(fieldPolicy); }; }, }; }, async willSendResponse(requestContext) { if (!calculateHttpHeaders) { return; } const { response, overallCachePolicy } = requestContext; const existingCacheControlHeader = parseExistingCacheControlHeader(response.http.headers.get('cache-control')); if (existingCacheControlHeader.kind === 'unparsable') { return; } const cachePolicy = (0, cachePolicy_js_1.newCachePolicy)(); cachePolicy.replace(overallCachePolicy); if (existingCacheControlHeader.kind === 'parsable-and-cacheable') { cachePolicy.restrict(existingCacheControlHeader.hint); } const policyIfCacheable = cachePolicy.policyIfCacheable(); if (policyIfCacheable && existingCacheControlHeader.kind !== 'uncacheable' && response.body.kind === 'single' && !response.body.singleResult.errors) { response.http.headers.set('cache-control', `max-age=${policyIfCacheable.maxAge}, ${policyIfCacheable.scope.toLowerCase()}`); } else if (calculateHttpHeaders !== 'if-cacheable') { response.http.headers.set('cache-control', CACHE_CONTROL_HEADER_UNCACHEABLE); } }, }; }, }); } exports.ApolloServerPluginCacheControl = ApolloServerPluginCacheControl; const CACHE_CONTROL_HEADER_CACHEABLE_REGEXP = /^max-age=(\d+), (public|private)$/; const CACHE_CONTROL_HEADER_UNCACHEABLE = 'no-store'; function parseExistingCacheControlHeader(header) { if (!header) { return { kind: 'no-header' }; } if (header === CACHE_CONTROL_HEADER_UNCACHEABLE) { return { kind: 'uncacheable' }; } const match = CACHE_CONTROL_HEADER_CACHEABLE_REGEXP.exec(header); if (!match) { return { kind: 'unparsable' }; } return { kind: 'parsable-and-cacheable', hint: { maxAge: +match[1], scope: match[2] === 'public' ? 'PUBLIC' : 'PRIVATE', }, }; } function cacheAnnotationFromDirectives(directives) { if (!directives) return undefined; const cacheControlDirective = directives.find((directive) => directive.name.value === 'cacheControl'); if (!cacheControlDirective) return undefined; if (!cacheControlDirective.arguments) return undefined; const maxAgeArgument = cacheControlDirective.arguments.find((argument) => argument.name.value === 'maxAge'); const scopeArgument = cacheControlDirective.arguments.find((argument) => argument.name.value === 'scope'); const inheritMaxAgeArgument = cacheControlDirective.arguments.find((argument) => argument.name.value === 'inheritMaxAge'); const scopeString = scopeArgument?.value?.kind === 'EnumValue' ? scopeArgument.value.value : undefined; const scope = scopeString === 'PUBLIC' || scopeString === 'PRIVATE' ? scopeString : undefined; if (inheritMaxAgeArgument?.value?.kind === 'BooleanValue' && inheritMaxAgeArgument.value.value) { return { inheritMaxAge: true, scope }; } return { maxAge: maxAgeArgument?.value?.kind === 'IntValue' ? parseInt(maxAgeArgument.value.value) : undefined, scope, }; } function cacheAnnotationFromType(t) { if (t.astNode) { const hint = cacheAnnotationFromDirectives(t.astNode.directives); if (hint) { return hint; } } if (t.extensionASTNodes) { for (const node of t.extensionASTNodes) { const hint = cacheAnnotationFromDirectives(node.directives); if (hint) { return hint; } } } return {}; } function cacheAnnotationFromField(field) { if (field.astNode) { const hint = cacheAnnotationFromDirectives(field.astNode.directives); if (hint) { return hint; } } return {}; } function isRestricted(hint) { return hint.maxAge !== undefined || hint.scope !== undefined; } //# sourceMappingURL=index.js.map