"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