UNPKG

@gql2ts/from-schema

Version:

convert a graphql schema to typescript interfaces

205 lines 9.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const graphql_1 = require("graphql"); const util_1 = require("@gql2ts/util"); const language_typescript_1 = require("@gql2ts/language-typescript"); const dedent = require("dedent"); const run = (schemaInput, optionsInput) => { const { generateEnumName, generateInterfaceName, generateTypeName, printType, formatInput, wrapList, formatEnum, typeBuilder, generateInterfaceDeclaration: gID, interfaceBuilder, addSemicolon, enumTypeBuilder, generateDocumentation, typeMap } = optionsInput.formats; const TYPE_MAP = Object.assign({}, language_typescript_1.DEFAULT_TYPE_MAP, (typeMap || {}), (optionsInput.typeMap || {})); const generateRootDataName = schema => { let rootNamespaces = []; const queryType = schema.getQueryType(); const mutationType = schema.getMutationType(); const subscriptionType = schema.getSubscriptionType(); if (queryType) { rootNamespaces.push(generateInterfaceName(queryType.name)); } if (mutationType) { rootNamespaces.push(generateInterfaceName(mutationType.name)); } if (subscriptionType) { rootNamespaces.push(generateInterfaceName(subscriptionType.name)); } return util_1.filterAndJoinArray(rootNamespaces, ' | '); }; const generateRootTypes = schema => util_1.filterAndJoinArray([ interfaceBuilder(generateInterfaceName('GraphQLResponseRoot'), gID([ formatInput('data', true, generateRootDataName(schema)), formatInput('errors', true, wrapList(generateInterfaceName('GraphQLResponseError'))) ])), interfaceBuilder(generateInterfaceName('GraphQLResponseError'), gID([ '/** Required for all errors */', formatInput('message', false, TYPE_MAP.String), formatInput('locations', true, wrapList(generateInterfaceName('GraphQLResponseErrorLocation'))), `/** 7.2.2 says 'GraphQL servers may provide additional entries to error' */`, formatInput('[propName: string]', false, TYPE_MAP.__DEFAULT), ])), interfaceBuilder(generateInterfaceName('GraphQLResponseErrorLocation'), gID([ formatInput('line', false, TYPE_MAP.Int), formatInput('column', false, TYPE_MAP.Int), ])) ], '\n\n'); const wrapWithDocumentation = (declaration, documentation) => dedent ` ${generateDocumentation(documentation)} ${declaration} `; function isInputField(field) { return (!!field.astNode && field.astNode.kind === 'InputValueDefinition') || !({}).hasOwnProperty.call(field, 'args'); } const generateTypeDeclaration = (description, name, possibleTypes) => wrapWithDocumentation(addSemicolon(typeBuilder(name, possibleTypes)), { description, tags: [] }) + '\n\n'; const typeNameDeclaration = name => addSemicolon(`__typename: "${name}"`); const generateInterfaceDeclaration = ({ name, description }, declaration, fields, additionalInfo, isInput) => { if (!isInput && !optionsInput.ignoreTypeNameDeclaration) { fields = [typeNameDeclaration(name), ...fields]; } return additionalInfo + wrapWithDocumentation(interfaceBuilder(declaration, gID(fields)), { description, tags: [] }); }; const generateEnumDeclaration = (description, name, enumValues) => { if (!enumTypeBuilder) { console.warn('Missing `enumTypeBuilder` from language file and falling back to using a type for enums. This new option was added in v1.5.0'); } const formattedEnum = formatEnum(enumValues, generateDocumentation); return wrapWithDocumentation((enumTypeBuilder || typeBuilder)(generateEnumName(name), // Only add semicolon when not using enum type builder enumTypeBuilder ? formattedEnum : addSemicolon(formattedEnum)), { description, tags: [] }); }; /** * TODO * - add support for custom types (via optional json file or something) */ const resolveInterfaceName = (type, isNonNull = false) => { if (util_1.isList(type)) { return { value: resolveInterfaceName(type.ofType, false), isList: true, isNonNull }; } if (util_1.isNonNullable(type)) { return resolveInterfaceName(type.ofType, true); } if (util_1.isScalar(type)) { return { value: TYPE_MAP[type.name] || TYPE_MAP.__DEFAULT, isList: false, isNonNull }; } if (graphql_1.isAbstractType(type)) { return { value: generateTypeName(type.name), isList: false, isNonNull }; } if (util_1.isEnum(type)) { return { value: generateEnumName(type.name), isList: false, isNonNull }; } return { value: generateInterfaceName(type.name), isList: false, isNonNull }; }; const typePrinter = (val) => { if (typeof val === 'string') { return val; } const isNonNull = val.isNonNull; if (val.isList) { return printType(wrapList(typePrinter(val.value)), isNonNull); } return printType(typePrinter(val.value), isNonNull); }; const fieldToDefinition = (field, isInput) => { const resolved = resolveInterfaceName(field.type, false); return formatInput(field.name, isInput && !resolved.isNonNull, typePrinter(resolved)); }; const generateArgumentDeclaration = (arg) => { const resolved = resolveInterfaceName(arg.type, false); return util_1.filterAndJoinArray([ generateDocumentation(util_1.buildDocumentation(arg)), formatInput(arg.name, !resolved.isNonNull, typePrinter(resolved)) ]); }; const generateArgumentsDeclaration = (field, parentName) => { if (isInputField(field) || !field.args || !field.args.length) { return null; } const fieldDeclaration = field.args.map(arg => generateArgumentDeclaration(arg)); const name = generateInterfaceName(`${field.name}_On_${parentName}`) + 'Arguments'; return interfaceBuilder(name, gID(fieldDeclaration)); }; const findRootType = type => { if (util_1.isList(type) || util_1.isNonNullable(type)) { return findRootType(type.ofType); } return type; }; const filterField = (field, ignoredTypes) => { let nestedType = findRootType(field.type); return !ignoredTypes.has(nestedType.name) && (!optionsInput.excludeDeprecatedFields || !field.isDeprecated); }; const generateAbstractTypeDeclaration = (type, ignoredTypes) => { const poss = schemaInput.getPossibleTypes(type); let possibleTypes = poss .filter(t => !ignoredTypes.has(t.name)) .map(t => generateInterfaceName(t.name)); return generateTypeDeclaration(type.description, generateTypeName(type.name), possibleTypes.join(' | ')); }; const typeToInterface = (type, ignoredTypes) => { if (util_1.isScalar(type)) { return null; } if (util_1.isUnion(type)) { return generateAbstractTypeDeclaration(type, ignoredTypes); } if (util_1.isEnum(type)) { return generateEnumDeclaration(type.description, type.name, type.getValues()); } const isInput = type instanceof graphql_1.GraphQLInputObjectType; const f1 = type.getFields(); const f = Object.keys(f1).map(k => f1[k]); const filteredFields = f.filter(field => filterField(field, ignoredTypes)); const fields = filteredFields .map(field => [generateDocumentation(util_1.buildDocumentation(field)), fieldToDefinition(field, isInput)]) .reduce((acc, val) => [...acc, ...val], []) .filter(Boolean); const interfaceDeclaration = generateInterfaceName(type.name); let additionalInfo = ''; if (graphql_1.isAbstractType(type)) { additionalInfo = generateAbstractTypeDeclaration(type, ignoredTypes); } return util_1.filterAndJoinArray([ generateInterfaceDeclaration(type, interfaceDeclaration, fields, additionalInfo, isInput), ...filteredFields.map(field => generateArgumentsDeclaration(field, type.name)) ], '\n\n'); }; const typesToInterfaces = (schema, options) => { const ignoredTypes = new Set(options.ignoredTypes); const types = schema.getTypeMap(); const typeArr = Object.keys(types).map(k => types[k]); const typeInterfaces = typeArr .filter(type => !type.name.startsWith('__')) // remove introspection types .filter(type => // remove ignored types !ignoredTypes.has(type.name)) .map(type => // convert to interface typeToInterface(type, ignoredTypes)); return util_1.filterAndJoinArray([ generateRootTypes(schema), ...typeInterfaces ], '\n\n'); }; return typesToInterfaces(schemaInput, optionsInput); }; exports.schemaToInterfaces = (schema, options = {}, formatters = {}) => run(util_1.schemaFromInputs(schema), Object.assign({}, options, { formats: Object.assign({}, language_typescript_1.DEFAULT_OPTIONS, formatters) })); exports.generateNamespace = (namespace, schema, options = {}, overrides = {}) => { const formatters = Object.assign({}, language_typescript_1.DEFAULT_OPTIONS, overrides); return formatters.postProcessor(formatters.generateNamespace(namespace, exports.schemaToInterfaces(schema, options, formatters))); }; //# sourceMappingURL=index.js.map