@gql2ts/from-schema
Version:
convert a graphql schema to typescript interfaces
205 lines • 9.93 kB
JavaScript
;
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