UNPKG

@omnigraph/json-schema

Version:

This package generates GraphQL Schema from JSON Schema and sample JSON request and responses. You can define your root field endpoints like below in your GraphQL Config for example;

2,364 lines • 115 kB
import { DefaultLogger, readFileOrUrl, defaultImportFn, sanitizeNameForGraphQL, getHeadersObj } from '@graphql-mesh/utils';
import { getInterpolationKeys, getInterpolatedHeadersFactory, stringInterpolator } from '@graphql-mesh/string-interpolation';
import { AnySchema, dereferenceObject, healJSONSchema, resolvePath, visitJSONSchema, referenceJSONSchema } from 'json-machete';
import toJsonSchema from 'to-json-schema';
import { process as process$1, util } from '@graphql-mesh/cross-helpers';
import { isListType, isNonNullType, isInputObjectType, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLError, getNamedType, isScalarType, isUnionType, GraphQLScalarType, Kind, GraphQLFloat, GraphQLBoolean, specifiedDirectives } from 'graphql';
import { GraphQLJSON, isSomeInputTypeComposer, ListComposer, SchemaComposer, ScalarTypeComposer, EnumTypeComposer, UnionTypeComposer, InterfaceTypeComposer, ObjectTypeComposer } from 'graphql-compose';
import { asArray, memoize1, inspect, memoize2 } from '@graphql-tools/utils';
import urlJoin from 'url-join';
import { stringify, parse } from 'qs';
import lodashSet from 'lodash.set';
import { FormData, File, Blob, fetch } from '@whatwg-node/fetch';
import { GraphQLJSON as GraphQLJSON$1, RegularExpression, GraphQLBigInt, GraphQLTimestamp, GraphQLUUID, GraphQLURL, GraphQLIPv6, GraphQLIPv4, GraphQLEmailAddress, GraphQLTime, GraphQLDateTime, GraphQLByte, GraphQLNonNegativeInt, GraphQLNonNegativeFloat, GraphQLPositiveInt, GraphQLPositiveFloat, GraphQLNonPositiveInt, GraphQLNonPositiveFloat, GraphQLNegativeInt, GraphQLNegativeFloat, GraphQLNonEmptyString } from 'graphql-scalars';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { pascalCase } from 'pascal-case';

function isPubSubOperationConfig(operationConfig) {
    return 'pubsubTopic' in operationConfig;
}
function getOperationMetadata(operationConfig) {
    let httpMethod;
    let operationType;
    let rootTypeName;
    if (isPubSubOperationConfig(operationConfig)) {
        httpMethod = null;
        operationType = 'subscription';
        rootTypeName = 'Subscription';
    }
    else {
        httpMethod = operationConfig.method;
        // Fix compability with Mesh handler
        operationType = operationConfig.type.toLowerCase();
        if (!httpMethod) {
            if (operationType === 'mutation') {
                httpMethod = 'POST';
            }
            else {
                httpMethod = 'GET';
            }
        }
        if (!rootTypeName) {
            if (httpMethod === 'GET') {
                rootTypeName = 'Query';
            }
        }
        rootTypeName = operationType === 'query' ? 'Query' : 'Mutation';
    }
    return {
        httpMethod,
        operationType,
        rootTypeName,
        fieldName: operationConfig.field,
    };
}
function isFileUpload(obj) {
    return typeof obj.createReadStream === 'function';
}

async function handleOperationResponseConfig(operationResponseConfig, { schemaHeaders, cwd, fetchFn, logger = new DefaultLogger('handleOperationResponseConfig'), }) {
    if (operationResponseConfig.responseSchema) {
        const schema = typeof operationResponseConfig.responseSchema === 'string'
            ? {
                $ref: operationResponseConfig.responseSchema,
                title: operationResponseConfig.responseTypeName,
            }
            : operationResponseConfig.responseSchema;
        if (operationResponseConfig.responseSample) {
            schema.examples = schema.examples || [operationResponseConfig.responseSample];
        }
        return schema;
    }
    else if (operationResponseConfig.responseSample) {
        const sample = typeof operationResponseConfig.responseSample === 'object'
            ? operationResponseConfig.responseSample
            : await readFileOrUrl(operationResponseConfig.responseSample, {
                cwd,
                fetch: fetchFn,
                logger,
                importFn: defaultImportFn,
                headers: schemaHeaders,
            }).catch((e) => {
                throw new Error(`responseSample - ${e.message}`);
            });
        const generatedSchema = toJsonSchema(sample, {
            required: false,
            objects: {
                additionalProperties: false,
            },
            strings: {
                detectFormat: true,
            },
            arrays: {
                mode: 'first',
            },
        });
        generatedSchema.title = operationResponseConfig.responseTypeName;
        generatedSchema.examples = [sample];
        return generatedSchema;
    }
    else {
        return AnySchema;
    }
}
async function getReferencedJSONSchemaFromOperations({ operations, cwd, schemaHeaders, ignoreErrorResponses, logger = new DefaultLogger('getReferencedJSONSchemaFromOperations'), fetchFn, baseUrl, operationHeaders, queryParams, }) {
    const finalJsonSchema = {
        type: 'object',
        title: '_schema',
        properties: {},
        required: ['query'],
    };
    for (const operationConfig of operations) {
        const { operationType, rootTypeName, fieldName } = getOperationMetadata(operationConfig);
        const rootTypeDefinition = (finalJsonSchema.properties[operationType] = finalJsonSchema.properties[operationType] || {
            type: 'object',
            title: rootTypeName,
            properties: {},
            readOnly: true,
        });
        rootTypeDefinition.properties = rootTypeDefinition.properties || {};
        const interpolationStrings = [
            ...Object.values(operationHeaders || {}),
            ...Object.values(queryParams || {}).map(val => val.toString()),
            baseUrl,
        ];
        if ('pubsubTopic' in operationConfig) {
            interpolationStrings.push(operationConfig.pubsubTopic);
        }
        if ('headers' in operationConfig) {
            interpolationStrings.push(...Object.values(operationConfig.headers || {}));
        }
        if ('path' in operationConfig) {
            interpolationStrings.push(operationConfig.path);
        }
        if ('responseByStatusCode' in operationConfig) {
            rootTypeDefinition.properties[fieldName] = rootTypeDefinition.properties[fieldName] || {};
            const statusCodeOneOfIndexMap = {};
            const responseSchemas = [];
            for (const statusCode in operationConfig.responseByStatusCode) {
                if (ignoreErrorResponses && !statusCode.startsWith('2')) {
                    continue;
                }
                const responseOperationConfig = operationConfig.responseByStatusCode[statusCode];
                const responseOperationSchema = await handleOperationResponseConfig(responseOperationConfig, {
                    cwd,
                    schemaHeaders,
                    fetchFn,
                    logger,
                });
                statusCodeOneOfIndexMap[statusCode] = responseSchemas.length;
                responseOperationSchema.title = responseOperationSchema.title || `${fieldName}_${statusCode}_response`;
                responseSchemas.push(responseOperationSchema);
            }
            if (responseSchemas.length === 1) {
                rootTypeDefinition.properties[fieldName] = responseSchemas[0];
            }
            else if (responseSchemas.length === 0) {
                rootTypeDefinition.properties[fieldName] = AnySchema;
            }
            else {
                rootTypeDefinition.properties[fieldName] = {
                    $comment: `statusCodeOneOfIndexMap:${JSON.stringify(statusCodeOneOfIndexMap)}`,
                    title: fieldName + '_response',
                    oneOf: responseSchemas,
                };
            }
        }
        else {
            rootTypeDefinition.properties[fieldName] = await handleOperationResponseConfig(operationConfig, {
                cwd,
                schemaHeaders,
                fetchFn,
                logger,
            });
        }
        const rootTypeInputPropertyName = operationType + 'Input';
        const rootInputTypeName = rootTypeName + 'Input';
        const rootTypeInputTypeDefinition = (finalJsonSchema.properties[rootTypeInputPropertyName] = finalJsonSchema
            .properties[rootTypeInputPropertyName] || {
            type: 'object',
            title: rootInputTypeName,
            properties: {},
            writeOnly: true,
        });
        const interpolationKeys = getInterpolationKeys(...interpolationStrings);
        if ('queryParamArgMap' in operationConfig) {
            interpolationKeys.push(...Object.values(operationConfig.queryParamArgMap).map(key => `args.${key}`));
        }
        for (const interpolationKey of interpolationKeys) {
            const interpolationKeyParts = interpolationKey.split('.');
            const initialObjectName = interpolationKeyParts.shift();
            if (initialObjectName === 'args') {
                rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                    title: `${rootTypeInputPropertyName}_${fieldName}`,
                    type: 'object',
                    properties: {},
                };
                const varName = interpolationKeyParts.shift();
                if (operationConfig.argTypeMap != null && varName in operationConfig.argTypeMap) {
                    const argTypeDef = operationConfig.argTypeMap[varName];
                    if (typeof argTypeDef === 'object') {
                        rootTypeInputTypeDefinition.properties[fieldName].properties[varName] = argTypeDef;
                    }
                    else {
                        rootTypeInputTypeDefinition.properties[fieldName].properties[varName] = {
                            $ref: argTypeDef,
                        };
                    }
                }
                else if (!rootTypeInputTypeDefinition.properties[fieldName].properties[varName]) {
                    rootTypeInputTypeDefinition.properties[fieldName].properties[varName] = {
                        type: 'string',
                    };
                }
            }
        }
        if ('binary' in operationConfig) {
            const generatedSchema = {
                type: 'string',
                format: 'binary',
            };
            rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                title: `${rootTypeInputPropertyName}_${fieldName}`,
                type: 'object',
                properties: {},
            };
            rootTypeInputTypeDefinition.properties[fieldName].properties.input = generatedSchema;
        }
        else if ('requestSchema' in operationConfig && operationConfig.requestSchema) {
            rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                title: `${rootTypeInputPropertyName}_${fieldName}`,
                type: 'object',
                properties: {},
            };
            rootTypeInputTypeDefinition.properties[fieldName].properties.input =
                typeof operationConfig.requestSchema === 'string'
                    ? {
                        $ref: operationConfig.requestSchema,
                        title: operationConfig.requestTypeName,
                    }
                    : operationConfig.requestSchema;
            if (operationConfig.requestSample) {
                rootTypeInputTypeDefinition.properties[fieldName].properties.input.examples = rootTypeInputTypeDefinition
                    .properties[fieldName].properties.input.examples || [operationConfig.requestSample];
            }
        }
        else if ('requestSample' in operationConfig) {
            const sample = typeof operationConfig.requestSample === 'object'
                ? operationConfig.requestSample
                : await readFileOrUrl(operationConfig.requestSample, {
                    cwd,
                    headers: schemaHeaders,
                    fetch: fetchFn,
                    logger,
                    importFn: defaultImportFn,
                }).catch((e) => {
                    throw new Error(`${operationConfig.field}.requestSample: ${operationConfig.requestSample}; ${e.message}`);
                });
            const generatedSchema = toJsonSchema(sample, {
                required: false,
                objects: {
                    additionalProperties: false,
                },
                strings: {
                    detectFormat: true,
                },
                arrays: {
                    mode: 'first',
                },
            });
            generatedSchema.title = operationConfig.requestTypeName;
            generatedSchema.examples = [sample];
            rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                title: `${rootTypeInputPropertyName}_${fieldName}`,
                type: 'object',
                properties: {},
            };
            rootTypeInputTypeDefinition.properties[fieldName].properties.input = generatedSchema;
        }
    }
    return finalJsonSchema;
}

async function getDereferencedJSONSchemaFromOperations({ operations, cwd = process$1.cwd(), logger, fetchFn, schemaHeaders, ignoreErrorResponses, baseUrl, operationHeaders, queryParams, }) {
    const referencedJSONSchema = await getReferencedJSONSchemaFromOperations({
        operations,
        cwd,
        schemaHeaders,
        ignoreErrorResponses,
        fetchFn,
        baseUrl,
        operationHeaders,
        queryParams,
    });
    logger.debug(`Dereferencing JSON Schema to resolve all $refs`);
    const schemaHeadersFactory = getInterpolatedHeadersFactory(schemaHeaders);
    const fullyDeferencedSchema = await dereferenceObject(referencedJSONSchema, {
        cwd,
        fetchFn,
        logger: logger.child('dereferenceObject'),
        headers: schemaHeadersFactory({ env: process$1.env }),
    });
    logger.debug(`Healing JSON Schema`);
    const healedSchema = await healJSONSchema(fullyDeferencedSchema, {
        logger: logger.child('healJSONSchema'),
    });
    return healedSchema;
}

function resolveDataByUnionInputType(data, type, schemaComposer) {
    var _a;
    if (data) {
        if (isListType(type)) {
            return asArray(data).map(elem => resolveDataByUnionInputType(elem, type.ofType, schemaComposer));
        }
        if (isNonNullType(type)) {
            return resolveDataByUnionInputType(data, type.ofType, schemaComposer);
        }
        if (isInputObjectType(type)) {
            const fieldMap = type.getFields();
            const isOneOf = schemaComposer.getAnyTC(type).getDirectiveByName('oneOf');
            data = asArray(data)[0];
            for (const propertyName in data) {
                const fieldName = sanitizeNameForGraphQL(propertyName);
                const field = fieldMap[fieldName];
                if (field) {
                    if (isOneOf) {
                        const resolvedData = resolveDataByUnionInputType(data[fieldName], field.type, schemaComposer);
                        return resolvedData;
                    }
                    const fieldData = data[fieldName];
                    data[fieldName] = undefined;
                    const realFieldName = ((_a = field.extensions) === null || _a === void 0 ? void 0 : _a.propertyName) || fieldName;
                    data[realFieldName] = resolveDataByUnionInputType(fieldData, field.type, schemaComposer);
                }
            }
        }
    }
    return data;
}

const defaultQsOptions = {
    indices: false,
};
const isListTypeOrNonNullListType = memoize1(function isListTypeOrNonNullListType(type) {
    if (isNonNullType(type)) {
        return isListType(type.ofType);
    }
    return isListType(type);
});
function createError(message, extensions) {
    return new GraphQLError(message, undefined, undefined, undefined, undefined, undefined, extensions);
}
function linkResolver(linkObjArgs, actualResolver, root, args, context, info) {
    for (const argKey in linkObjArgs) {
        const argInterpolation = linkObjArgs[argKey];
        const actualValue = typeof argInterpolation === 'string'
            ? stringInterpolator.parse(argInterpolation, {
                root,
                args,
                context,
                info,
                env: process$1.env,
            })
            : argInterpolation;
        lodashSet(args, argKey, actualValue);
    }
    return actualResolver(root, args, context, info);
}
const responseMetadataType = new GraphQLObjectType({
    name: 'ResponseMetadata',
    fields: {
        url: { type: GraphQLString },
        method: { type: GraphQLString },
        status: { type: GraphQLInt },
        statusText: { type: GraphQLString },
        headers: { type: GraphQLJSON },
        body: { type: GraphQLJSON },
    },
});
async function addExecutionLogicToComposer(name, { schemaComposer, fetch: globalFetch, logger, operations, operationHeaders, baseUrl, pubsub: globalPubsub, queryParams, queryStringOptions = {}, }) {
    logger.debug(`Attaching execution logic to the schema`);
    queryStringOptions = { ...defaultQsOptions, ...queryStringOptions };
    const linkResolverMapByField = new Map();
    for (const operationConfig of operations) {
        const { httpMethod, rootTypeName, fieldName } = getOperationMetadata(operationConfig);
        const operationLogger = logger.child(`${rootTypeName}.${fieldName}`);
        const rootTypeComposer = schemaComposer[rootTypeName];
        const field = rootTypeComposer.getField(fieldName);
        if (isPubSubOperationConfig(operationConfig)) {
            field.description = operationConfig.description || `PubSub Topic: ${operationConfig.pubsubTopic}`;
            field.subscribe = (root, args, context, info) => {
                const pubsub = (context === null || context === void 0 ? void 0 : context.pubsub) || globalPubsub;
                if (!pubsub) {
                    return new GraphQLError(`You should have PubSub defined in either the config or the context!`);
                }
                const interpolationData = { root, args, context, info, env: process$1.env };
                let pubsubTopic = stringInterpolator.parse(operationConfig.pubsubTopic, interpolationData);
                if (pubsubTopic.startsWith('webhook:')) {
                    const [, expectedMethod, expectedUrl] = pubsubTopic.split(':');
                    const expectedPath = new URL(expectedUrl, 'http://localhost').pathname;
                    pubsubTopic = `webhook:${expectedMethod}:${expectedPath}`;
                }
                operationLogger.debug(`=> Subscribing to pubSubTopic: ${pubsubTopic}`);
                return pubsub.asyncIterator(pubsubTopic);
            };
            field.resolve = root => {
                operationLogger.debug('Received ', root, ' from ', operationConfig.pubsubTopic);
                return root;
            };
        }
        else if (operationConfig.path) {
            if (process$1.env.DEBUG === '1' || process$1.env.DEBUG === 'fieldDetails') {
                field.description = `
>**Method**: \`${operationConfig.method}\`
>**Base URL**: \`${baseUrl}\`
>**Path**: \`${operationConfig.path}\`
${operationConfig.description || ''}
`;
            }
            else {
                field.description = operationConfig.description;
            }
            field.resolve = async (root, args, context, info) => {
                var _a, _b, _c, _d, _e;
                operationLogger.debug(`=> Resolving`);
                const interpolationData = { root, args, context, env: process$1.env };
                const interpolatedBaseUrl = stringInterpolator.parse(baseUrl, interpolationData);
                const interpolatedPath = stringInterpolator.parse(operationConfig.path, interpolationData);
                let fullPath = urlJoin(interpolatedBaseUrl, interpolatedPath);
                const operationHeadersObj = typeof operationHeaders === 'function'
                    ? await operationHeaders(interpolationData, operationConfig)
                    : operationHeaders;
                const headers = {};
                for (const headerName in operationHeadersObj) {
                    const nonInterpolatedValue = operationHeadersObj[headerName];
                    const interpolatedValue = stringInterpolator.parse(nonInterpolatedValue, interpolationData);
                    if (interpolatedValue) {
                        headers[headerName.toLowerCase()] = interpolatedValue;
                    }
                }
                if (operationConfig === null || operationConfig === void 0 ? void 0 : operationConfig.headers) {
                    for (const headerName in operationConfig.headers) {
                        const nonInterpolatedValue = operationConfig.headers[headerName];
                        const interpolatedValue = stringInterpolator.parse(nonInterpolatedValue, interpolationData);
                        if (interpolatedValue) {
                            headers[headerName.toLowerCase()] = interpolatedValue;
                        }
                    }
                }
                const requestInit = {
                    method: httpMethod,
                    headers,
                };
                // Handle binary data
                if ('binary' in operationConfig) {
                    const binaryUpload = await args.input;
                    if (isFileUpload(binaryUpload)) {
                        const readable = binaryUpload.createReadStream();
                        const chunks = [];
                        for await (const chunk of readable) {
                            for (const byte of chunk) {
                                chunks.push(byte);
                            }
                        }
                        requestInit.body = new Uint8Array(chunks);
                        const [, contentType] = Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
                        if (!contentType) {
                            headers['content-type'] = binaryUpload.mimetype;
                        }
                    }
                    requestInit.body = binaryUpload;
                }
                else {
                    if (operationConfig.requestBaseBody != null) {
                        args.input = args.input || {};
                        for (const key in operationConfig.requestBaseBody) {
                            const configValue = operationConfig.requestBaseBody[key];
                            if (typeof configValue === 'string') {
                                const value = stringInterpolator.parse(configValue, interpolationData);
                                lodashSet(args.input, key, value);
                            }
                            else {
                                args.input[key] = configValue;
                            }
                        }
                    }
                    // Resolve union input
                    const input = (args.input = resolveDataByUnionInputType(args.input, (_c = (_b = (_a = field.args) === null || _a === void 0 ? void 0 : _a.input) === null || _b === void 0 ? void 0 : _b.type) === null || _c === void 0 ? void 0 : _c.getType(), schemaComposer));
                    if (input != null) {
                        const [, contentType] = Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
                        if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/x-www-form-urlencoded')) {
                            requestInit.body = stringify(input, queryStringOptions);
                        }
                        else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('multipart/form-data')) {
                            delete headers['content-type'];
                            delete headers['Content-Type'];
                            const formData = new FormData();
                            for (const key in input) {
                                const inputValue = input[key];
                                if (inputValue != null) {
                                    let formDataValue;
                                    if (typeof inputValue === 'object') {
                                        if (inputValue instanceof File) {
                                            formDataValue = inputValue;
                                        }
                                        else if (inputValue.name && inputValue instanceof Blob) {
                                            formDataValue = new File([inputValue], inputValue.name, { type: inputValue.type });
                                        }
                                        else if (inputValue.arrayBuffer) {
                                            const arrayBuffer = await inputValue.arrayBuffer();
                                            if (inputValue.name) {
                                                formDataValue = new File([arrayBuffer], inputValue.name, { type: inputValue.type });
                                            }
                                            else {
                                                formDataValue = new Blob([arrayBuffer], { type: inputValue.type });
                                            }
                                        }
                                        else {
                                            formDataValue = JSON.stringify(inputValue);
                                        }
                                    }
                                    else {
                                        formDataValue = inputValue.toString();
                                    }
                                    formData.append(key, formDataValue);
                                }
                            }
                            requestInit.body = formData;
                        }
                        else {
                            requestInit.body = typeof input === 'object' ? JSON.stringify(input) : input;
                        }
                    }
                }
                if (queryParams) {
                    for (const queryParamName in queryParams) {
                        if (args != null &&
                            operationConfig.queryParamArgMap != null &&
                            queryParamName in operationConfig.queryParamArgMap &&
                            operationConfig.queryParamArgMap[queryParamName] in args) {
                            continue;
                        }
                        const interpolatedQueryParam = stringInterpolator.parse(queryParams[queryParamName].toString(), interpolationData);
                        const queryParamsString = stringify({
                            [queryParamName]: interpolatedQueryParam,
                        }, {
                            ...queryStringOptions,
                            ...(_d = operationConfig.queryStringOptionsByParam) === null || _d === void 0 ? void 0 : _d[queryParamName],
                        });
                        fullPath += fullPath.includes('?') ? '&' : '?';
                        fullPath += queryParamsString;
                    }
                }
                if (operationConfig.queryParamArgMap) {
                    for (const queryParamName in operationConfig.queryParamArgMap) {
                        const argName = operationConfig.queryParamArgMap[queryParamName];
                        let argValue = args[argName];
                        if (argValue != null) {
                            // Somehow it doesn't serialize URLs so we need to do it manually.
                            if (argValue instanceof URL) {
                                argValue = argValue.toString();
                            }
                            const opts = {
                                ...queryStringOptions,
                                ...(_e = operationConfig.queryStringOptionsByParam) === null || _e === void 0 ? void 0 : _e[queryParamName],
                            };
                            let queryParamObj = argValue;
                            if (Array.isArray(argValue) || !opts.destructObject) {
                                queryParamObj = {
                                    [queryParamName]: argValue,
                                };
                            }
                            const queryParamsString = stringify(queryParamObj, opts);
                            fullPath += fullPath.includes('?') ? '&' : '?';
                            fullPath += queryParamsString;
                        }
                    }
                }
                operationLogger.debug(`=> Fetching `, fullPath, `=>`, requestInit);
                const fetch = (context === null || context === void 0 ? void 0 : context.fetch) || globalFetch;
                if (!fetch) {
                    return createError(`You should have fetch defined in either the config or the context!`, {
                        url: fullPath,
                        method: httpMethod,
                    });
                }
                // Trick to pass `sourceName` to the `fetch` function for tracing
                const response = await fetch(fullPath, requestInit, context, {
                    ...info,
                    sourceName: name,
                });
                // If return type is a file
                if (field.type.getTypeName() === 'File') {
                    return response.blob();
                }
                const responseText = await response.text();
                operationLogger.debug(`=> Received`, {
                    headers: response.headers,
                    text: responseText,
                });
                let responseJson;
                try {
                    responseJson = JSON.parse(responseText);
                }
                catch (error) {
                    const returnNamedGraphQLType = getNamedType(field.type.getType());
                    // The result might be defined as scalar
                    if (isScalarType(returnNamedGraphQLType)) {
                        operationLogger.debug(` => Return type is not a JSON so returning ${responseText}`);
                        return responseText;
                    }
                    else if (response.status === 204) {
                        responseJson = {};
                    }
                    else {
                        logger.debug(`Unexpected response in ${fieldName};\n\t${responseText}`);
                        return createError(`Unexpected response`, {
                            url: fullPath,
                            method: httpMethod,
                            responseText,
                            error,
                        });
                    }
                }
                if (!response.status.toString().startsWith('2')) {
                    const returnNamedGraphQLType = getNamedType(field.type.getType());
                    if (!isUnionType(returnNamedGraphQLType)) {
                        return createError(`HTTP Error: ${response.status}, Could not invoke operation ${operationConfig.method} ${operationConfig.path}`, {
                            method: httpMethod,
                            url: fullPath,
                            statusCode: response.status,
                            statusText: response.statusText,
                            responseBody: responseJson,
                        });
                    }
                }
                operationLogger.debug(`Returning `, responseJson);
                // Sometimes API returns an array but the return type is not an array
                const isListReturnType = isListTypeOrNonNullListType(field.type.getType());
                const isArrayResponse = Array.isArray(responseJson);
                if (isListReturnType && !isArrayResponse) {
                    operationLogger.debug(`Response is not array but return type is list. Normalizing the response`);
                    responseJson = [responseJson];
                }
                if (!isListReturnType && isArrayResponse) {
                    operationLogger.debug(`Response is array but return type is not list. Normalizing the response`);
                    responseJson = responseJson[0];
                }
                const addResponseMetadata = (obj) => {
                    if (typeof obj !== 'object') {
                        return obj;
                    }
                    Object.defineProperties(obj, {
                        $field: {
                            get() {
                                return operationConfig.field;
                            },
                        },
                        $url: {
                            get() {
                                return fullPath.split('?')[0];
                            },
                        },
                        $method: {
                            get() {
                                return httpMethod;
                            },
                        },
                        $statusCode: {
                            get() {
                                return response.status;
                            },
                        },
                        $statusText: {
                            get() {
                                return response.statusText;
                            },
                        },
                        $headers: {
                            get() {
                                return requestInit.headers;
                            },
                        },
                        $request: {
                            get() {
                                return new Proxy({}, {
                                    get(_, requestProp) {
                                        switch (requestProp) {
                                            case 'query':
                                                return parse(fullPath.split('?')[1]);
                                            case 'path':
                                                return new Proxy(args, {
                                                    get(_, prop) {
                                                        var _a;
                                                        return args[prop] || ((_a = args.input) === null || _a === void 0 ? void 0 : _a[prop]) || (obj === null || obj === void 0 ? void 0 : obj[prop]);
                                                    },
                                                    has(_, prop) {
                                                        return prop in args || (args.input && prop in args.input) || (obj === null || obj === void 0 ? void 0 : obj[prop]);
                                                    },
                                                });
                                            case 'header':
                                                return getHeadersObj(requestInit.headers);
                                            case 'body':
                                                return requestInit.body;
                                        }
                                    },
                                });
                            },
                        },
                        $response: {
                            get() {
                                return new Proxy({}, {
                                    get(_, responseProp) {
                                        switch (responseProp) {
                                            case 'header':
                                                return getHeadersObj(response.headers);
                                            case 'body':
                                                return obj;
                                            case 'query':
                                                return parse(fullPath.split('?')[1]);
                                            case 'path':
                                                return new Proxy(args, {
                                                    get(_, prop) {
                                                        var _a;
                                                        return args[prop] || ((_a = args.input) === null || _a === void 0 ? void 0 : _a[prop]) || (obj === null || obj === void 0 ? void 0 : obj[prop]);
                                                    },
                                                    has(_, prop) {
                                                        return prop in args || (args.input && prop in args.input) || (obj === null || obj === void 0 ? void 0 : obj[prop]);
                                                    },
                                                });
                                        }
                                    },
                                });
                            },
                        },
                    });
                    return obj;
                };
                operationLogger.debug(`Adding response metadata to the response object`);
                return Array.isArray(responseJson)
                    ? responseJson.map(obj => addResponseMetadata(obj))
                    : addResponseMetadata(responseJson);
            };
            const handleLinkMap = (linkMap, typeTC) => {
                for (const linkName in linkMap) {
                    typeTC.addFields({
                        [linkName]: () => {
                            const linkObj = linkMap[linkName];
                            let linkResolverFieldMap = linkResolverMapByField.get(operationConfig.field);
                            if (!linkResolverFieldMap) {
                                linkResolverFieldMap = {};
                                linkResolverMapByField.set(operationConfig.field, linkResolverFieldMap);
                            }
                            let targetField;
                            try {
                                targetField = schemaComposer.Query.getField(linkObj.fieldName);
                            }
                            catch (_a) {
                                try {
                                    targetField = schemaComposer.Mutation.getField(linkObj.fieldName);
                                }
                                catch (_b) { }
                            }
                            if (!targetField) {
                                logger.debug(`Field ${linkObj.fieldName} not found in ${name} for link ${linkName}`);
                            }
                            linkResolverFieldMap[linkName] = (root, args, context, info) => linkResolver(linkObj.args, targetField.resolve, root, args, context, info);
                            return {
                                ...targetField,
                                args: linkObj.args ? {} : targetField.args,
                                description: linkObj.description || targetField.description,
                                // Pick the correct link resolver if there are many link for the same return type used by different operations
                                resolve: (root, args, context, info) => {
                                    var _a;
                                    const linkResolverFieldMapForCurrentField = (_a = linkResolverMapByField.get(root.$field)) !== null && _a !== void 0 ? _a : linkResolverFieldMap;
                                    return linkResolverFieldMapForCurrentField[linkName](root, args, context, info);
                                },
                            };
                        },
                    });
                }
            };
            if ('links' in operationConfig) {
                const typeTC = schemaComposer.getOTC(field.type.getTypeName());
                handleLinkMap(operationConfig.links, typeTC);
            }
            if ('exposeResponseMetadata' in operationConfig && operationConfig.exposeResponseMetadata) {
                const typeTC = schemaComposer.getOTC(field.type.getTypeName());
                typeTC.addFields({
                    _response: {
                        type: responseMetadataType,
                        resolve: root => ({
                            url: root.$url,
                            headers: root.$response.header,
                            method: root.$method,
                            status: root.$statusCode,
                            statusText: root.$statusText,
                            body: root.$response.body,
                        }),
                    },
                });
            }
            if ('responseByStatusCode' in operationConfig) {
                const unionOrSingleTC = schemaComposer.getAnyTC(getNamedType(field.type.getType()));
                const types = 'getTypes' in unionOrSingleTC ? unionOrSingleTC.getTypes() : [unionOrSingleTC];
                const statusCodeOneOfIndexMap = unionOrSingleTC.getExtension('statusCodeOneOfIndexMap') || {};
                for (const statusCode in operationConfig.responseByStatusCode) {
                    const responseConfig = operationConfig.responseByStatusCode[statusCode];
                    if (responseConfig.links || responseConfig.exposeResponseMetadata) {
                        const typeTCThunked = types[statusCodeOneOfIndexMap[statusCode] || 0];
                        const originalName = typeTCThunked.getTypeName();
                        let typeTC = schemaComposer.getAnyTC(originalName);
                        if (!('addFieldArgs' in typeTC)) {
                            typeTC = schemaComposer.createObjectTC({
                                name: `${operationConfig.field}_${statusCode}_response`,
                                fields: {
                                    [originalName]: {
                                        type: typeTC,
                                        resolve: root => root,
                                    },
                                },
                            });
                            // If it is a scalar or enum type, it cannot be a union type, so we can set it directly
                            types[0] = typeTC;
                            field.type = typeTC;
                        }
                        if (responseConfig.exposeResponseMetadata) {
                            typeTC.addFields({
                                _response: {
                                    type: responseMetadataType,
                                    resolve: root => root.$response,
                                },
                            });
                        }
                        if (responseConfig.links) {
                            handleLinkMap(responseConfig.links, typeTC);
                        }
                    }
                }
            }
        }
    }
    logger.debug(`Building the executable schema.`);
    return schemaComposer;
}

function getValidTypeName({ schemaComposer, isInput, subSchema, }) {
    if (!subSchema.title) {
        throw new Error('Missing title for schema; ' + inspect(subSchema));
    }
    const sanitizedName = sanitizeNameForGraphQL(isInput ? subSchema.title + '_Input' : subSchema.title);
    if (schemaComposer.has(sanitizedName)) {
        let i = 2;
        while (schemaComposer.has(sanitizedName + i)) {
            i++;
        }
        return sanitizedName + i;
    }
    return sanitizedName;
}

function getStringScalarWithMinMaxLength({ schemaComposer, subSchema, }) {
    const name = getValidTypeName({
        schemaComposer,
        isInput: false,
        subSchema,
    });
    function coerceString(value) {
        if (value != null) {
            const vStr = value.toString();
            if (typeof subSchema.minLength !== 'undefined' && vStr.length < subSchema.minLength) {
                throw new Error(`${name} cannot be less than ${subSchema.minLength} but given ${vStr}`);
            }
            if (typeof subSchema.maxLength !== 'undefined' && vStr.length > subSchema.maxLength) {
                throw new Error(`${name} cannot be more than ${subSchema.maxLength} but given ${vStr}`);
            }
            return vStr;
        }
    }
    return schemaComposer.createScalarTC({
        name,
        description: subSchema.description,
        serialize: coerceString,
        parseValue: coerceString,
        parseLiteral: ast => {
            if ('value' in ast) {
                return coerceString(ast.value);
            }
            return null;
        },
        extensions: {
            codegenScalarType: 'string',
        },
    });
}

const JSONSchemaStringFormats = [
    'date',
    'hostname',
    'regex',
    'json-pointer',
    'relative-json-pointer',
    'uri-reference',
    'uri-template',
];
function getJSONSchemaStringFormatScalarMap(ajv) {
    const map = new Map();
    for (const format of JSONSchemaStringFormats) {
        const schema = {
            type: 'string',
            format,
        };
        let validate;
        try {
            validate = ajv.compile(schema);
        }
        catch (e) {
            validate = (value) => ajv.validate(schema, value);
        }
        const coerceString = (value) => {
            if (validate(value)) {
                return value;
            }
            throw new Error(`Expected ${format} but got: ${value}`);
        };
        const scalar = new GraphQLScalarType({
            name: pascalCase(format),
            description: `Represents ${format} values`,
            serialize: coerceString,
            parseValue: coerceString,
            parseLiteral: ast => {
                if (ast.kind === Kind.STRING) {
                    return coerceString(ast.value);
                }
                throw new Error(`Expected string in ${format} format but got: ${ast.value}`);
            },
            extensions: {
                codegenScalarType: 'string',
            },
        });
        map.set(format, scalar);
    }
    return map;
}

function getTypeResolverFromOutputTCs(ajv, outputTypeComposers, subSchemaAndTypeComposers, statusCodeOneOfIndexMap) {
    var _a;
    const statusCodeTypeMap = new Map();
    for (const statusCode in statusCodeOneOfIndexMap) {
        statusCodeTypeMap.set(statusCode.toString(), outputTypeComposers[statusCodeOneOfIndexMap[statusCode]]);
    }
    const discriminatorField = (_a = subSchemaAndTypeComposers.discriminator) === null || _a === void 0 ? void 0 : _a.propertyName;
    return function resolveType(data, context, info) {
        if (data.__typename) {
            return data.__typename;
        }
        else if (discriminatorField != null && data[discriminatorField]) {
            return data[discriminatorField];
        }
        if (data.$statusCode && statusCodeOneOfIndexMap) {
            const type = statusCodeTypeMap.get(data.$statusCode.toString()) || statusCodeTypeMap.get('default');
            if (type) {
                if ('getFields' in type) {
                    return type.getTypeName();
                }
                else {
                    return type.getResolveType()(data, context, info, type.getType());
                }
            }
        }
        const validationErrors = {};
        const dataKeys = typeof data === 'object'
            ? Object.keys(data)
                // Remove metadata fields used to pass data
                .filter(property => !property.toString().startsWith('$'))
            : null;
        const allOutputTypeComposers = outputTypeComposers.flatMap(typeComposer => 'getFields' in typeComposer ? typeComposer : typeComposer.getTypeComposers());
        for (const outputTypeComposer of allOutputTypeComposers) {
            const typeName = outputTypeComposer.getTypeName();
            if (dataKeys != null) {
                const typeFields = outputTypeComposer.getFieldNames();
                if (dataKeys.length <= typeFields.length &&
                    dataKeys.every(property => typeFields.includes(property.toString()))) {
                    return typeName;
                }
            }
            else {
                const validateFn = outputTypeComposer.getExtension('validateWithJSONSchema');
                if (validateFn) {
                    const isValid = validateFn(data);
                    if (isValid) {
                        return typeName;
                    }
                    validationErrors[typeName] = ajv.errors || validateFn.errors;
                }
            }
        }
        if (data.$response) {
            const error = new GraphQLError(`HTTP Error: ${data.$statusCode}`, undefined, undefined, undefined, undefined, undefined, {
                $url: data.$url,
                $method: data.$method,
                $statusCode: data.$statusCode,
                $request: {
                    query: data.$request.query,
                    header: data.$request.header,
                },
                $response: {
                    header: data.$response.header,
                    body: data.$response.body,
                },
            });
            return error;
        }
        const error = new GraphQLError(`Received data doesn't met the union`, null, null, null, null, null, {
            validationErrors,
        });
        return error;
    };
}

const ONE_OF_DEFINITION = /* GraphQL */ `
  directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
`;
function getContainerTC(schemaComposer, output) {
    const containerTypeName = `${output.getTypeName()}_container`;
    return schemaComposer.getOrCreateOTC(containerTypeName, otc => otc.addFields({
        [output.getTypeName()]: {
            type: output,
            resolve: root => root,
        },
    }));
}
function getUnionTypeComposers({ schemaComposer, ajv, typeComposersList, subSchemaAndTypeComposers, logger, }) {
    if (typeComposersList.length === 1) {
        return typeComposersList[0];
    }
    const unionInputFields = {};
    const outputTypeComposers = [];
    typeComposersList.forEach(typeComposers => {
        const { input, output } = typeComposers;
        if (isSomeInputTypeComposer(output)) {
            outputTypeComposers.push(getContainerTC(schemaComposer, output));
        }
        else {
            outputTypeComposers.push(output);
        }
        if (input) {
            unionInputFields[input.getTypeName()] = {
                type: input,
            };
        }
        if (!input) {
            logger.debug(`No input type composer found for ${output.getTypeName()}, skipping...`);
        }
    });
    if (Object.keys(unionInputFields).length === 1) {
        subSchemaAndTypeComposers.input = Object.values(unionInputFields)[0].type;
    }
    else {
        subSchemaAndTypeComposers.input.addFields(unionInputFields);
        if (!schemaComposer.hasDirective('oneOf')) {
            schemaComposer.addTypeDefs(ONE_OF_DEFINITION);
        }
    }
    const dedupSet = new Set(outputTypeComposers);
    if (dedupSet.size === 1) {
        subSchemaAndTypeComposers.output = outputTypeComposers[0];
    }
    else {
        const resolveType = getTypeResolverFromOutputTCs(ajv, outputTypeComposers, subSchemaAndTypeComposers, subSchemaAndTypeComposers.output.getExtension('statusCodeOneOfIndexMap'));
        subSchemaAndTypeComposers.output.setResolveType(resolveType);
        for (const outputTypeComposer of outputTypeComposers) {
            if ('getFields' in outputTypeComposer) {
                subSchemaAndTypeComposers.output.addType(outputTypeComposer);
            }
            else {
                for (const possibleType of outputTypeComposer.getTypes()) {
                    subSchemaAndTypeComposers.output.addType(possibleType);
                }
            }
        }
    }
    return {
        input: subSchemaAndTypeComposers.input,
        output: subSchemaAndTypeComposers.output,
        nullable: subSchemaAndTypeComposers.nullable,
        readOnly: subSchemaAndTypeComposers.readOnly,
        writeOnly: subSchemaAndTypeComposers.writeOnly,
    };
}

function getGenericJSONScalar({ isInput, subSchema, schemaComposer, validateWithJSONSchema, }) {
    function coerceGenericJSONScalar(value) {
        if (!validateWithJSONSchema(value)) {
            throw new Error(`${util.inspect(value)} is not valid!`);
        }
        return value;
    }
    const name = getValidTypeName({
        schemaComposer,
        isInput,
        subSchema,
    });
    return schemaComposer.createScalarTC({
        name,
        description: subSchema.description,
        serialize: coerceGenericJSONScalar,
        parseValue: coerceGenericJSONScalar,
        parseLiteral(...args) {
            const value = GraphQLJSON.parseLiteral(...args);
            return coerceGenericJSONScalar(value);
        },
        extensions: {
            codegenScalarType: 'any',
            examples: subSchema.examples,
            default: subSchema.default,
        },
    });
}

const ajvMemoizedCompile = memoize2(function ajvCompile(ajv, jsonSchema) {
    const schema = typeof jsonSchema === 'object'
        ? {
            ...jsonSchema,
            $schema: undefined,
        }
        : jsonSchema;
    try {
        return ajv.compile(schema);
    }
    catch (_a) {
        // eslint-disable-next-line no-inner-declarations
        function validateFn(value) {
            return ajv.validate(schema, value);
        }
        Object.defineProperty(validateFn, 'errors', {
            get() {
                return ajv.errors;
            },
        });
        return validateFn;
    }
});
function getValidateFnForSchemaPath(ajv, path, schema) {
    const subSchema = resolvePath(path, schema);
    const fn = function validateFn(data) {
        const ajvValidateFn = ajvMemoizedCompile(ajv, subSchema);
        return ajvValidateFn(data);
    };
    Object.defineProperty(fn, 'errors', {
        get() {
            return ajvMemoizedCompile(ajv, subSchema).errors;
        },
    });
    return fn;
}

/* eslint-disable no-case-declarations */
const isListTC = memoize1(function isListTC(type) {
    return type instanceof ListComposer;
});
const GraphQLVoid = new GraphQLScalarType({
    name: 'Void',
    description: 'Represents empty values',
    serialize: () => '',
    extensions: {
        codegenScalarType: 'void',
    },
});
const GraphQLFile = new GraphQLScalarType({
    name: 'File',
    description: 'The `File` scalar type represents a file upload.',
    extensions: {
        codegenScalarType: 'File',
    },
});
function getComposerFromJSONSchema(schema, logger) {
    const schemaComposer = new SchemaComposer();
    const ajv = new Ajv({
        strict: false,
    });
    addFormats(ajv);
    const formatScalarMap = getJSONSchemaStringFormatScalarMap(ajv);
    const rootInputTypeNameComposerMap = {
        QueryInput: () => schemaComposer.Query,
        MutationInput: () => schemaComposer.Mutation,
        SubscriptionInput: () => schemaComposer.Subscription,
    };
    return visitJSONSchema(schema, {
        enter(subSchema, { path, visitedSubschemaResultMap }) {
            var _a;
            if (typeof subSchema === 'boolean' || subSchema.title === 'Any') {
                const typeComposer = schemaComposer.getAnyTC(GraphQLJSON$1);
                return subSchema
                    ? {
                        input: typeComposer,
                        output: typeComposer,
                    }
                    : undefined;
            }
            const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema);
            if (!subSchema) {
                throw new Error(`Something is wrong with ${path}`);
            }
            if (subSchema.pattern) {
                let regexp;
                try {
                    regexp = new RegExp(subSchema.pattern);
                    let codegenScalarType;
                    switch (subSchema.type) {
                        case 'string':
                            codegenScalarType = 'string';
                            break;
                        case 'number':
                            codegenScalarType = 'number';
                            break;
                        case 'integer':
                            if (subSchema.format === 'int64') {
                                codegenScalarType = 'bigint';
                            }
                            else {
                                codegenScalarType = 'number';
                            }
                            break;
                    }
                    const scalarType = new RegularExpression(getValidTypeName({
                        schemaComposer,
                        isInput: false,
                        subSchema,
                    }), regexp, {
                        description: subSchema.description,
                    });
                    scalarType.extensions = scalarType.extensions || {};
                    scalarType.extensions.codegenScalarType = codegenScalarType;
                    const typeComposer = schemaComposer.getAnyTC(scalarType);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                    };
                }
                catch (e) {
                    logger.debug(`RegExp: ${subSchema.pattern} is not valid`, e);
                }
            }
            if (subSchema.const) {
                const scalarTypeName = getValidTypeName({
                    schemaComposer,
                    isInput: false,
                    subSchema,
                });
                const typeComposer = schemaComposer.createEnumTC({
                    name: scalarTypeName,
                    values: {
                        [sanitizeNameForGraphQL(subSchema.const.toString())]: {
                            value: subSchema.const,
                        },
                    },
                    extensions: {
                        codegenScalarType: JSON.stringify(subSchema.const),
                        examples: [subSchema.const],
                        default: subSchema.const,
                    },
                });
                return {
                    input: typeComposer,
                    output: typeComposer,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                };
            }
            if (subSchema.enum && subSchema.type !== 'boolean') {
                const values = {};
                for (const value of subSchema.enum) {
                    let enumKey = sanitizeNameForGraphQL(value.toString());
                    if (enumKey === 'false' || enumKey === 'true' || enumKey === 'null') {
                        enumKey = enumKey.toUpperCase();
                    }
                    if (typeof enumKey === 'string' && enumKey.length === 0) {
                        enumKey = '_';
                    }
                    values[enumKey] = {
                        // Falsy values are ignored by GraphQL
                        // eslint-disable-next-line no-unneeded-ternary
                        value: value ? value : value === null || value === void 0 ? void 0 : value.toString(),
                    };
                }
                const typeComposer = schemaComposer.createEnumTC({
                    name: getValidTypeName({
                        schemaComposer,
                        isInput: false,
                        subSchema,
                    }),
                    values,
                    description: subSchema.description,
                    extensions: {
                        examples: subSchema.examples,
                        default: subSchema.default,
                    },
                });
                return {
                    input: typeComposer,
                    output: typeComposer,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                    default: subSchema.default,
                };
            }
            if (Array.isArray(subSchema.type)) {
                const validTypes = subSchema.type.filter((typeName) => typeName !== 'null');
                if (validTypes.length === 1) {
                    subSchema.type = validTypes[0];
                    // continue with the single type
                }
                else if (validTypes.length === 0) {
                    const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                else {
                    const typeComposer = getGenericJSONScalar({
                        isInput: true,
                        subSchema,
                        schemaComposer,
                        validateWithJSONSchema,
                    });
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
            }
            if (subSchema.format) {
                switch (subSchema.format) {
                    case 'byte': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLByte);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            default: subSchema.default,
                        };
                    }
                    case 'binary': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLFile);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            default: subSchema.default,
                        };
                    }
                    case 'date-time': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLDateTime);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'time': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLTime);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'email': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLEmailAddress);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'ipv4': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLIPv4);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'ipv6': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLIPv6);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'uri': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLURL);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'uuid': {
                        const typeComposer = schemaComposer.getAnyTC(GraphQLUUID);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'unix-time': {
                        const typeComposer = schemaComposer.createScalarTC(GraphQLTimestamp);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'int64': {
                        const typeComposer = schemaComposer.createScalarTC(GraphQLBigInt);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'int32': {
                        const typeComposer = schemaComposer.createScalarTC(GraphQLInt);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    case 'decimal':
                    case 'float': {
                        const typeComposer = schemaComposer.createScalarTC(GraphQLFloat);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    default: {
                        const formatScalar = formatScalarMap.get(subSchema.format);
                        if (formatScalar) {
                            const typeComposer = schemaComposer.getAnyTC(formatScalar);
                            return {
                                input: typeComposer,
                                output: typeComposer,
                                description: subSchema.description,
                                nullable: subSchema.nullable,
                                readOnly: subSchema.readOnly,
                                writeOnly: subSchema.writeOnly,
                                default: subSchema.default,
                            };
                        }
                    }
                }
            }
            if (subSchema.minimum === 0) {
                const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNonNegativeInt : GraphQLNonNegativeFloat);
                return {
                    input: typeComposer,
                    output: typeComposer,
                    description: subSchema.description,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                    default: subSchema.default,
                };
            }
            else if (subSchema.minimum > 0) {
                const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLPositiveInt : GraphQLPositiveFloat);
                return {
                    input: typeComposer,
                    output: typeComposer,
                    description: subSchema.description,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                    default: subSchema.default,
                };
            }
            if (subSchema.maximum === 0) {
                const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNonPositiveInt : GraphQLNonPositiveFloat);
                return {
                    input: typeComposer,
                    output: typeComposer,
                    description: subSchema.description,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                    default: subSchema.default,
                };
            }
            else if (subSchema.maximum < 0) {
                const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNegativeInt : GraphQLNegativeFloat);
                return {
                    input: typeComposer,
                    output: typeComposer,
                    description: subSchema.description,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                    default: subSchema.default,
                };
            }
            if (subSchema.maximum > Number.MAX_SAFE_INTEGER || subSchema.minimum < Number.MIN_SAFE_INTEGER) {
                const typeComposer = schemaComposer.getAnyTC(GraphQLBigInt);
                return {
                    input: typeComposer,
                    output: typeComposer,
                    description: subSchema.description,
                    nullable: subSchema.nullable,
                    readOnly: subSchema.readOnly,
                    writeOnly: subSchema.writeOnly,
                    default: subSchema.default,
                };
            }
            switch (subSchema.type) {
                case 'boolean': {
                    const typeComposer = schemaComposer.getAnyTC(GraphQLBoolean);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        description: subSchema.description,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                case 'null': {
                    const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        description: subSchema.description,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                case 'integer': {
                    const typeComposer = schemaComposer.getAnyTC(GraphQLInt);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        description: subSchema.description,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                case 'number': {
                    const typeComposer = schemaComposer.getAnyTC(GraphQLFloat);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        description: subSchema.description,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                case 'string': {
                    if (subSchema.minLength === 1 && subSchema.maxLength == null) {
                        const tc = schemaComposer.getAnyTC(GraphQLNonEmptyString);
                        return {
                            input: tc,
                            output: tc,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    if (subSchema.minLength || subSchema.maxLength) {
                        const scalarType = getStringScalarWithMinMaxLength({
                            schemaComposer,
                            subSchema,
                        });
                        const typeComposer = schemaComposer.getAnyTC(scalarType);
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    const typeComposer = schemaComposer.getAnyTC(GraphQLString);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        description: subSchema.description,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                case 'array':
                    if (typeof subSchema.items === 'object' && Object.keys(subSchema.items).length > 0) {
                        return {
                            // These are filled after enter
                            get input() {
                                const typeComposers = visitedSubschemaResultMap.get(subSchema.items);
                                return typeComposers.input.getTypePlural();
                            },
                            get output() {
                                const typeComposers = visitedSubschemaResultMap.get(subSchema.items);
                                return typeComposers.output.getTypePlural();
                            },
                            ...subSchema,
                        };
                    }
                    if (subSchema.contains) {
                        // Scalars cannot be in union type
                        const typeComposer = getGenericJSONScalar({
                            schemaComposer,
                            isInput: false,
                            subSchema,
                            validateWithJSONSchema,
                        }).getTypePlural();
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                    // If it doesn't have any clue
                    {
                        // const typeComposer = getGenericJSONScalar({
                        //   schemaComposer,
                        //   isInput: false,
                        //   subSchema,
                        //   validateWithJSONSchema,
                        // }).getTypePlural();
                        const typeComposer = schemaComposer.getAnyTC(GraphQLJSON$1).getTypePlural();
                        return {
                            input: typeComposer,
                            output: typeComposer,
                            description: subSchema.description,
                            nullable: subSchema.nullable,
                            readOnly: subSchema.readOnly,
                            writeOnly: subSchema.writeOnly,
                            default: subSchema.default,
                        };
                    }
                case 'object': {
                    switch (subSchema.title) {
                        case '_schema':
                            return {
                                output: schemaComposer,
                                ...subSchema,
                            };
                        case 'Query':
                            return {
                                output: schemaComposer.Query,
                                ...subSchema,
                            };
                        case 'Mutation':
                            return {
                                output: schemaComposer.Mutation,
                                ...subSchema,
                            };
                        case 'Subscription':
                            if (path.startsWith('/properties/subscription')) {
                                return {
                                    output: schemaComposer.Subscription,
                                    ...subSchema,
                                };
                            }
                            subSchema.title = 'Subscription_';
                            break;
                    }
                }
            }
            if (subSchema.oneOf && !subSchema.properties) {
                let statusCodeOneOfIndexMap;
                if ((_a = subSchema.$comment) === null || _a === void 0 ? void 0 : _a.startsWith('statusCodeOneOfIndexMap:')) {
                    const statusCodeOneOfIndexMapStr = subSchema.$comment.replace('statusCodeOneOfIndexMap:', '');
                    statusCodeOneOfIndexMap = JSON.parse(statusCodeOneOfIndexMapStr);
                }
                const input = schemaComposer.createInputTC({
                    name: getValidTypeName({
                        schemaComposer,
                        isInput: true,
                        subSchema,
                    }),
                    fields: {},
                    directives: [
                        {
                            name: 'oneOf',
                        },
                    ],
                });
                const output = schemaComposer.createUnionTC({
                    name: getValidTypeName({
                        schemaComposer,
                        isInput: false,
                        subSchema,
                    }),
                    description: subSchema.description,
                    types: [],
                    extensions: {
                        statusCodeOneOfIndexMap,
                    },
                });
                return {
                    input,
                    output,
                    ...subSchema,
                };
            }
            if (subSchema.properties || subSchema.allOf || subSchema.anyOf || subSchema.additionalProperties) {
                if (subSchema.title === 'Any') {
                    const typeComposer = schemaComposer.getAnyTC(GraphQLJSON$1);
                    return {
                        input: typeComposer,
                        output: typeComposer,
                        description: subSchema.description,
                        nullable: subSchema.nullable,
                        readOnly: subSchema.readOnly,
                        writeOnly: subSchema.writeOnly,
                        default: subSchema.default,
                    };
                }
                const config = {
                    name: getValidTypeName({
                        schemaComposer,
                        isInput: false,
                        subSchema,
                    }),
                    description: subSchema.description,
                    fields: {},
                    extensions: {
                        validateWithJSONSchema,
                        examples: subSchema.examples,
                        default: subSchema.default,
                    },
                };
                return {
                    input: schemaComposer.createInputTC({
                        name: getValidTypeName({
                            schemaComposer,
                            isInput: true,
                            subSchema,
                        }),
                        description: subSchema.description,
                        fields: {},
                        extensions: {
                            examples: subSchema.examples,
                            default: subSchema.default,
                        },
                    }),
                    output: subSchema.discriminator
                        ? schemaComposer.createInterfaceTC({
                            ...config,
                            resolveType(root) {
                                return root[subSchema.discriminator];
                            },
                        })
                        : schemaComposer.createObjectTC(config),
                    ...subSchema,
                    ...(subSchema.properties ? { properties: { ...subSchema.properties } } : {}),
                    ...(subSchema.allOf ? { allOf: [...subSchema.allOf] } : {}),
                    ...(subSchema.additionalProperties
                        ? {
                            additionalProperties: subSchema.additionalProperties === true ? true : { ...subSchema.additionalProperties },
                        }
                        : {}),
                };
            }
            return subSchema;
        },
        leave(subSchemaAndTypeComposers, { path }) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
            const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema);
            const subSchemaOnly = {
                ...subSchemaAndTypeComposers,
                input: undefined,
                output: undefined,
            };
            if (subSchemaAndTypeComposers.oneOf && !subSchemaAndTypeComposers.properties) {
                const isPlural = subSchemaAndTypeComposers.oneOf.some(({ output }) => 'ofType' in output);
                if (isPlural) {
                    const { input, output } = getUnionTypeComposers({
                        schemaComposer,
                        ajv,
                        typeComposersList: subSchemaAndTypeComposers.oneOf.map(({ input, output }) => ({
                            input: input.ofType || input,
                            output: output.ofType || output,
                        })),
                        subSchemaAndTypeComposers,
                        logger,
                    });
                    return {
                        input: input.getTypePlural(),
                        output: output.getTypePlural(),
                        nullable: subSchemaAndTypeComposers.nullable,
                        default: subSchemaAndTypeComposers.default,
                        readOnly: subSchemaAndTypeComposers.readOnly,
                        writeOnly: subSchemaAndTypeComposers.writeOnly,
                    };
                }
                return getUnionTypeComposers({
                    schemaComposer,
                    ajv,
                    typeComposersList: subSchemaAndTypeComposers.oneOf,
                    subSchemaAndTypeComposers,
                    logger,
                });
            }
            const fieldMap = {};
            const inputFieldMap = {};
            let isList = false;
            if (subSchemaAndTypeComposers.allOf) {
                let ableToUseGraphQLInputObjectType = true;
                for (const maybeTypeComposers of subSchemaAndTypeComposers.allOf) {
                    let { input: inputTypeComposer, output: outputTypeComposer } = maybeTypeComposers;
                    if (inputTypeComposer instanceof ListComposer) {
                        isList = true;
                        inputTypeComposer = inputTypeComposer.ofType;
                    }
                    if (inputTypeComposer instanceof ScalarTypeComposer || inputTypeComposer instanceof EnumTypeComposer) {
                        ableToUseGraphQLInputObjectType = false;
                    }
                    else {
                        const inputTypeElemFieldMap = inputTypeComposer.getFields();
                        for (const fieldName in inputTypeElemFieldMap) {
                            const field = inputTypeElemFieldMap[fieldName];
                            inputFieldMap[fieldName] = field;
                        }
                    }
                    if (isSomeInputTypeComposer(outputTypeComposer)) {
                        fieldMap[outputTypeComposer.getTypeName()] = {
                            type: outputTypeComposer,
                            resolve: root => root,
                        };
                    }
                    else if (outputTypeComposer instanceof UnionTypeComposer) {
                        const outputTCElems = outputTypeComposer.getTypes();
                        for (const outputTCElem of outputTCElems) {
                            const outputTypeElemFieldMap = outputTCElem.getFields();
                            for (const fieldName in outputTypeElemFieldMap) {
                                const field = outputTypeElemFieldMap[fieldName];
                                fieldMap[fieldName] = field;
                            }
                        }
                    }
                    else {
                        if (outputTypeComposer instanceof InterfaceTypeComposer) {
                            subSchemaAndTypeComposers.output.addInterface(outputTypeComposer);
                        }
                        const typeElemFieldMap = outputTypeComposer.getFields();
                        for (const fieldName in typeElemFieldMap) {
                            const field = typeElemFieldMap[fieldName];
                            fieldMap[fieldName] = field;
                        }
                    }
                }
                subSchemaAndTypeComposers.output.addFields(fieldMap);
                subSchemaAndTypeComposers.output.setExtensions({
                    validateWithJSONSchema,
                    examples: subSchemaAndTypeComposers.examples,
                    default: subSchemaAndTypeComposers.default,
                });
                if (ableToUseGraphQLInputObjectType) {
                    subSchemaAndTypeComposers.input.addFields(inputFieldMap);
                    subSchemaAndTypeComposers.input.setExtensions({
                        examples: subSchemaAndTypeComposers.examples,
                        default: subSchemaAndTypeComposers.default,
                    });
                }
                else {
                    subSchemaAndTypeComposers.input = schemaComposer.getAnyTC(GraphQLJSON$1);
                }
            }
            if (subSchemaAndTypeComposers.anyOf) {
                // It should not have `required` because it is `anyOf` not `allOf`
                let ableToUseGraphQLInputObjectType = true;
                for (const typeComposers of subSchemaAndTypeComposers.anyOf) {
                    let { input: inputTypeComposer, output: outputTypeComposer } = typeComposers;
                    if (inputTypeComposer instanceof ListComposer || outputTypeComposer instanceof ListComposer) {
                        isList = true;
                        inputTypeComposer = inputTypeComposer.ofType;
                        outputTypeComposer = outputTypeComposer.ofType;
                    }
                    if (inputTypeComposer instanceof ScalarTypeComposer || inputTypeComposer instanceof EnumTypeComposer) {
                        ableToUseGraphQLInputObjectType = false;
                    }
                    else {
                        const inputTypeElemFieldMap = inputTypeComposer.getFields();
                        for (const fieldName in inputTypeElemFieldMap) {
                            // In case of conflict set it to JSON
                            // TODO: But instead we can convert that field into a oneOf of all possible types
                            if (inputFieldMap[fieldName]) {
                                inputFieldMap[fieldName] = {
                                    type: schemaComposer.getAnyTC(GraphQLJSON$1),
                                };
                            }
                            else {
                                const field = inputTypeElemFieldMap[fieldName];
                                inputFieldMap[fieldName] = isNonNullType(field.type.getType())
                                    ? {
                                        ...field,
                                        type: () => field.type.ofType,
                                    }
                                    : field;
                            }
                        }
                    }
                    if (outputTypeComposer instanceof ScalarTypeComposer) {
                        const typeName = outputTypeComposer.getTypeName();
                        // In case of conflict set it to JSON
                        // TODO: But instead we can convert that field into a union of all possible types
                        if (fieldMap[typeName] && fieldMap[typeName].type.getTypeName() !== typeName) {
                            fieldMap[typeName] = {
                                type: schemaComposer.getAnyTC(GraphQLJSON$1),
                            };
                        }
                        else {
                            fieldMap[typeName] = {
                                type: outputTypeComposer,
                                resolve: root => root,
                            };
                        }
                    }
                    else {
                        const typeElemFieldMap = outputTypeComposer.getFields();
                        for (const fieldName in typeElemFieldMap) {
                            // In case of conflict set it to JSON
                            // TODO: But instead we can convert that field into a union of all possible types
                            const field = typeElemFieldMap[fieldName];
                            const existingField = fieldMap[fieldName];
                            fieldMap[fieldName] = {
                                ...field,
                                type: () => {
                                    const fieldType = field.type.getType();
                                    const namedType = getNamedType(fieldType);
                                    if (existingField) {
                                        const existingFieldType = existingField.type();
                                        const existingNamedType = getNamedType(existingFieldType);
                                        if (existingNamedType.name !== namedType.name) {
                                            return schemaComposer.getAnyTC(GraphQLJSON$1);
                                        }
                                    }
                                    return field.type.getType();
                                },
                            };
                        }
                    }
                }
                subSchemaAndTypeComposers.output.addFields(fieldMap);
                subSchemaAndTypeComposers.output.setExtensions({
                    validateWithJSONSchema,
                    examples: subSchemaAndTypeComposers.examples,
                    default: subSchemaAndTypeComposers.default,
                });
                if (ableToUseGraphQLInputObjectType) {
                    subSchemaAndTypeComposers.input.addFields(inputFieldMap);
                    subSchemaAndTypeComposers.input.setExtensions({
                        examples: subSchemaAndTypeComposers.examples,
                        default: subSchemaAndTypeComposers.default,
                    });
                }
                else {
                    subSchemaAndTypeComposers.input = schemaComposer.getAnyTC(GraphQLJSON$1);
                }
            }
            switch (subSchemaAndTypeComposers.type) {
                case 'object':
                    if (subSchemaAndTypeComposers.properties) {
                        for (const propertyName in subSchemaAndTypeComposers.properties) {
                            // TODO: needs to be fixed
                            if (propertyName === 'additionalProperties') {
                                continue;
                            }
                            const fieldName = sanitizeNameForGraphQL(propertyName);
                            if (!subSchemaAndTypeComposers.properties[propertyName].writeOnly) {
                                fieldMap[fieldName] = {
                                    type: () => {
                                        var _a;
                                        const typeComposers = subSchemaAndTypeComposers.properties[propertyName];
                                        let nullable = true;
                                        if ((_a = subSchemaAndTypeComposers.required) === null || _a === void 0 ? void 0 : _a.includes(propertyName)) {
                                            nullable = false;
                                        }
                                        // Nullable has more priority
                                        if (typeComposers.nullable === false) {
                                            nullable = false;
                                        }
                                        if (typeComposers.nullable === true) {
                                            nullable = true;
                                        }
                                        return !nullable ? typeComposers.output.getTypeNonNull() : typeComposers.output;
                                    },
                                    // Make sure you get the right property
                                    resolve: root => {
                                        const typeComposers = subSchemaAndTypeComposers.properties[propertyName];
                                        const actualFieldObj = root[propertyName];
                                        if (actualFieldObj != null) {
                                            const isArray = Array.isArray(actualFieldObj);
                                            const isListType = isListTC(typeComposers.output);
                                            if (isListType && !isArray) {
                                                return [actualFieldObj];
                                            }
                                            else if (!isListTC(typeComposers.output) && isArray) {
                                                return actualFieldObj[0];
                                            }
                                        }
                                        return actualFieldObj;
                                    },
                                    description: subSchemaAndTypeComposers.properties[propertyName].description ||
                                        ((_a = subSchemaAndTypeComposers.properties[propertyName].output) === null || _a === void 0 ? void 0 : _a.description),
                                };
                            }
                            if (!subSchemaAndTypeComposers.properties[propertyName].readOnly) {
                                inputFieldMap[fieldName] = {
                                    type: () => {
                                        var _a, _b;
                                        const typeComposers = subSchemaAndTypeComposers.properties[propertyName];
                                        let nullable = true;
                                        if ((_a = subSchemaAndTypeComposers.required) === null || _a === void 0 ? void 0 : _a.includes(propertyName)) {
                                            nullable = false;
                                        }
                                        // Nullable has more priority
                                        if (typeComposers.nullable === false) {
                                            nullable = false;
                                        }
                                        if (typeComposers.nullable === true) {
                                            nullable = true;
                                        }
                                        return !nullable ? (_b = typeComposers.input) === null || _b === void 0 ? void 0 : _b.getTypeNonNull() : typeComposers.input;
                                    },
                                    // Let execution logic know what is the expected propertyName
                                    extensions: {
                                        propertyName,
                                    },
                                    description: subSchemaAndTypeComposers.properties[propertyName].description ||
                                        ((_b = subSchemaAndTypeComposers.properties[propertyName].input) === null || _b === void 0 ? void 0 : _b.description),
                                    defaultValue: ((_c = subSchemaAndTypeComposers.properties[propertyName]) === null || _c === void 0 ? void 0 : _c.default) ||
                                        ((_e = (_d = subSchemaAndTypeComposers.properties[propertyName]) === null || _d === void 0 ? void 0 : _d.extensions) === null || _e === void 0 ? void 0 : _e.default) ||
                                        ((_g = (_f = subSchemaAndTypeComposers.properties[propertyName]) === null || _f === void 0 ? void 0 : _f.input) === null || _g === void 0 ? void 0 : _g.default),
                                };
                            }
                        }
                    }
                    if (subSchemaAndTypeComposers.additionalProperties) {
                        if (typeof subSchemaAndTypeComposers.additionalProperties === 'object' &&
                            subSchemaAndTypeComposers.additionalProperties.output instanceof ObjectTypeComposer) {
                            if (Object.keys(fieldMap).length === 0) {
                                return subSchemaAndTypeComposers.additionalProperties;
                            }
                            else {
                                const outputTC = subSchemaAndTypeComposers.additionalProperties.output;
                                const outputTCFieldMap = outputTC.getFields();
                                for (const fieldName in outputTCFieldMap) {
                                    fieldMap[fieldName] = outputTCFieldMap[fieldName];
                                }
                                const inputTC = subSchemaAndTypeComposers.additionalProperties.input;
                                const inputTCFieldMap = inputTC.getFields();
                                for (const fieldName in inputTCFieldMap) {
                                    inputFieldMap[fieldName] = inputTCFieldMap[fieldName];
                                }
                            }
                        }
                        else if (Object.keys(fieldMap).length > 0) {
                            fieldMap.additionalProperties = {
                                type: GraphQLJSON$1,
                                resolve: (root) => root,
                            };
                        }
                        else {
                            const typeComposer = schemaComposer.getAnyTC(GraphQLJSON$1);
                            schemaComposer.delete((_j = (_h = subSchemaAndTypeComposers.input) === null || _h === void 0 ? void 0 : _h.getTypeName) === null || _j === void 0 ? void 0 : _j.call(_h));
                            schemaComposer.delete((_l = (_k = subSchemaAndTypeComposers.output) === null || _k === void 0 ? void 0 : _k.getTypeName) === null || _l === void 0 ? void 0 : _l.call(_k));
                            return {
                                input: typeComposer,
                                output: typeComposer,
                                description: subSchemaAndTypeComposers.description,
                                nullable: subSchemaAndTypeComposers.nullable,
                                default: subSchemaAndTypeComposers.default,
                                readOnly: subSchemaAndTypeComposers.readOnly,
                                writeOnly: subSchemaAndTypeComposers.writeOnly,
                            };
                        }
                    }
                    if (subSchemaAndTypeComposers.title in rootInputTypeNameComposerMap) {
                        const typeComposer = rootInputTypeNameComposerMap[subSchemaAndTypeComposers.title]();
                        for (const fieldName in inputFieldMap) {
                            let inputTC = inputFieldMap[fieldName].type();
                            if ('ofType' in inputTC) {
                                inputTC = inputTC.ofType;
                            }
                            if (!inputTC.getFields) {
                                console.log(fieldName);
                            }
                            typeComposer.addFieldArgs(fieldName, inputTC.getFields());
                        }
                        return {
                            output: typeComposer,
                        };
                    }
                    let output = subSchemaAndTypeComposers.output;
                    if (Object.keys(fieldMap).length === 0) {
                        output = schemaComposer.getAnyTC(GraphQLJSON$1);
                    }
                    else if ('addFields' in output) {
                        output.addFields(fieldMap);
                    }
                    let input = subSchemaAndTypeComposers.input;
                    if (Object.keys(inputFieldMap).length === 0) {
                        input = schemaComposer.getAnyTC(GraphQLJSON$1);
                    }
                    else if (input != null && 'addFields' in input) {
                        input.addFields(inputFieldMap);
                    }
                    if (isList) {
                        input = input.List;
                        output = output.List;
                    }
                    return {
                        input,
                        output,
                        nullable: subSchemaAndTypeComposers.nullable,
                        default: subSchemaAndTypeComposers.default,
                        readOnly: subSchemaAndTypeComposers.readOnly,
                        writeOnly: subSchemaAndTypeComposers.writeOnly,
                    };
            }
            if (subSchemaAndTypeComposers.input || subSchemaAndTypeComposers.output) {
                return {
                    input: subSchemaAndTypeComposers.input,
                    output: subSchemaAndTypeComposers.output,
                    description: subSchemaAndTypeComposers.description,
                    nullable: subSchemaAndTypeComposers.nullable,
                    default: subSchemaAndTypeComposers.default,
                    readOnly: subSchemaAndTypeComposers.readOnly,
                    writeOnly: subSchemaAndTypeComposers.writeOnly,
                };
            }
            else {
                logger.debug(`GraphQL Type cannot be created for this JSON Schema definition;`, {
                    subSchema: subSchemaOnly,
                    path,
                });
                const typeComposer = schemaComposer.getAnyTC(GraphQLJSON$1);
                return {
                    input: typeComposer,
                    output: typeComposer,
                    description: subSchemaAndTypeComposers.description,
                    nullable: subSchemaAndTypeComposers.nullable,
                    readOnly: subSchemaAndTypeComposers.readOnly,
                    writeOnly: subSchemaAndTypeComposers.writeOnly,
                    default: subSchemaAndTypeComposers.default,
                };
            }
        },
    });
}

async function getGraphQLSchemaFromDereferencedJSONSchema(name, opts) {
    const { fullyDeferencedSchema, fetch, logger, operations, operationHeaders, baseUrl, pubsub, queryParams, queryStringOptions, } = opts;
    logger.debug(`Generating GraphQL Schema from the bundled JSON Schema`);
    const visitorResult = await getComposerFromJSONSchema(fullyDeferencedSchema, logger.child('getComposerFromJSONSchema'));
    const schemaComposerWithoutExecutionLogic = visitorResult.output;
    if (!(schemaComposerWithoutExecutionLogic instanceof SchemaComposer)) {
        throw new Error('The visitor result should be a SchemaComposer instance.');
    }
    // graphql-compose doesn't add @defer and @stream to the schema
    for (const directive of specifiedDirectives) {
        schemaComposerWithoutExecutionLogic.addDirective(directive);
    }
    const schemaComposerWithExecutionLogic = await addExecutionLogicToComposer(name, {
        schemaComposer: schemaComposerWithoutExecutionLogic,
        fetch,
        logger,
        operations,
        operationHeaders,
        baseUrl,
        pubsub,
        queryParams,
        queryStringOptions,
    });
    if (schemaComposerWithExecutionLogic.Query.getFieldNames().length === 0) {
        schemaComposerWithExecutionLogic.Query.addFields({
            dummy: {
                type: 'String',
                resolve: () => 'dummy',
            },
        });
    }
    return schemaComposerWithExecutionLogic.buildSchema();
}

async function loadGraphQLSchemaFromJSONSchemas(name, options) {
    const logger = options.logger || new DefaultLogger(name);
    const operations = options.operations;
    const cwd = options.cwd || process.cwd();
    const fullyDeferencedSchema = await getDereferencedJSONSchemaFromOperations({
        operations,
        operationHeaders: typeof options.operationHeaders === 'object' ? options.operationHeaders : {},
        queryParams: options.queryParams,
        baseUrl: options.baseUrl,
        cwd,
        logger,
        fetchFn: options.fetch,
        schemaHeaders: options.schemaHeaders,
        ignoreErrorResponses: options.ignoreErrorResponses,
    });
    const graphqlSchema = await getGraphQLSchemaFromDereferencedJSONSchema(name, {
        fullyDeferencedSchema,
        fetch: options.fetch,
        logger,
        operations,
        operationHeaders: options.operationHeaders,
        baseUrl: options.baseUrl,
        pubsub: options.pubsub,
        queryParams: options.queryParams,
        queryStringOptions: options.queryStringOptions,
    });
    return graphqlSchema;
}

async function createBundle(name, { baseUrl, operations, schemaHeaders, operationHeaders, queryParams, cwd = process.cwd(), fetch: fetch$1 = fetch, logger = new DefaultLogger(name), ignoreErrorResponses = false, }) {
    logger.debug(`Creating the dereferenced schema from operations config`);
    const dereferencedSchema = await getDereferencedJSONSchemaFromOperations({
        operations,
        cwd,
        logger,
        fetchFn: fetch$1,
        schemaHeaders,
        ignoreErrorResponses,
        baseUrl,
        operationHeaders,
        queryParams,
    });
    logger.debug(`Creating references from dereferenced schema`);
    const referencedSchema = await referenceJSONSchema(dereferencedSchema);
    logger.debug(`Bundle generation finished`);
    return {
        name,
        baseUrl,
        operations,
        operationHeaders: typeof operationHeaders === 'object' ? operationHeaders : {},
        referencedSchema,
    };
}
/**
 * Generates a local GraphQLSchema instance from
 * previously generated JSON Schema bundle
 */
async function getGraphQLSchemaFromBundle({ name, baseUrl: bundledBaseUrl, operations, operationHeaders: bundledOperationHeaders = {}, referencedSchema, }, { cwd = process.cwd(), fetch: fetch$1 = fetch, pubsub, logger = new DefaultLogger(name), baseUrl: overwrittenBaseUrl, operationHeaders: additionalOperationHeaders = {}, queryParams, queryStringOptions, } = {}) {
    logger.info(`Dereferencing the bundle`);
    const fullyDeferencedSchema = await dereferenceObject(referencedSchema, {
        cwd,
        fetchFn: fetch$1,
        logger,
    });
    let operationHeaders = {};
    if (typeof additionalOperationHeaders === 'function') {
        operationHeaders = async (resolverData, operationConfig) => {
            const result = await additionalOperationHeaders(resolverData, operationConfig);
            return {
                ...bundledOperationHeaders,
                ...result,
            };
        };
    }
    else {
        operationHeaders = {
            ...bundledOperationHeaders,
            ...additionalOperationHeaders,
        };
    }
    logger.info(`Creating the GraphQL Schema from dereferenced schema`);
    return getGraphQLSchemaFromDereferencedJSONSchema(name, {
        fullyDeferencedSchema,
        fetch: fetch$1,
        pubsub,
        logger,
        baseUrl: overwrittenBaseUrl || bundledBaseUrl,
        operations,
        operationHeaders,
        queryParams,
        queryStringOptions,
    });
}

export default loadGraphQLSchemaFromJSONSchemas;
export { createBundle, getComposerFromJSONSchema, getDereferencedJSONSchemaFromOperations, getGraphQLSchemaFromBundle, getGraphQLSchemaFromDereferencedJSONSchema, getReferencedJSONSchemaFromOperations, loadGraphQLSchemaFromJSONSchemas };