UNPKG

@giraphql/converter

Version:

A converter for generating GiraphQL SchemaBuilder code from GraphQL SDL

614 lines (613 loc) 23.9 kB
import { GraphQLEnumType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType, } from 'graphql'; import { Project, StructureKind, VariableDeclarationKind, } from 'ts-morph'; const builtins = ["Boolean", "Int", "Float", "String", "ID"]; function unwrap(type) { if (type instanceof GraphQLNonNull) { return unwrap(type.ofType); } if (type instanceof GraphQLList) { return unwrap(type.ofType); } return type; } function isRecursive(type, seen = []) { if (!(type instanceof GraphQLObjectType || type instanceof GraphQLInputObjectType || type instanceof GraphQLInterfaceType)) { return false; } const fieldMap = type.getFields(); const fields = Object.keys(fieldMap).map((name) => fieldMap[name]); return fields.some((field) => { const fieldType = unwrap(field.type); if (fieldType.name === type.name) { return true; } if (seen.includes(fieldType.name)) { return true; } return isRecursive(fieldType, [...seen, type.name]); }); } export default class GiraphQLConverter { constructor(schema, { types } = {}) { this.project = new Project(); this.schema = schema; this.sourcefile = this.project.createSourceFile("./codegen/schema.ts"); this.types = types !== null && types !== void 0 ? types : null; this.createSchemaTypes(); } createSchemaTypes() { const typeMap = this.schema.getTypeMap(); const gqlTypes = Object.keys(typeMap).map((typeName) => typeMap[typeName]); if (!this.types) { this.sourcefile.addImportDeclaration({ kind: StructureKind.ImportDeclaration, moduleSpecifier: "@giraphql/core", defaultImport: "SchemaBuilder", }); this.sourcefile.addStatements((writer) => writer.blankLine()); this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, declarations: [ { kind: StructureKind.VariableDeclaration, name: "builder", initializer: (writer) => { writer.writeLine("new SchemaBuilder<{"); writer.indent(() => { this.writeTypeInfo(writer); }); writer.writeLine("}>({})"); }, }, ], }); } gqlTypes.forEach((type) => { if (type.name.startsWith("__") || builtins.includes(type.name)) { return; } if (this.types && !this.types.includes(type.name)) { return; } if (type instanceof GraphQLUnionType) { this.unionType(type); } else if (type instanceof GraphQLEnumType) { this.enumType(type); } else if (type instanceof GraphQLScalarType) { this.scalarType(type); } }); gqlTypes.forEach((type) => { if (this.types && !this.types.includes(type.name)) { return; } if (type instanceof GraphQLInputObjectType) { this.inputType(type); } }); gqlTypes.forEach((type) => { if (this.types && !this.types.includes(type.name)) { return; } if (this.types && !this.types.includes(type.name)) { return; } if (type instanceof GraphQLInterfaceType) { this.interfaceType(type); } }); gqlTypes.forEach((type) => { if (this.types && !this.types.includes(type.name)) { return; } if (type.name.startsWith("__")) { return; } if (type instanceof GraphQLObjectType) { switch (type.name) { case "Query": { this.queryType(type); break; } case "Mutation": { this.mutationType(type); break; } case "Subscription": { this.subscriptionType(type); break; } default: { this.objectType(type); } } } }); if (!this.types) { this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, isExported: true, declarations: [ { kind: StructureKind.VariableDeclaration, name: "schema", initializer: (writer) => { writer.writeLine("builder.toSchema({})"); }, }, ], }); } } queryType(type) { this.sourcefile.addStatements((writer) => { writer.writeLine("builder.queryType({"); writer.indent(() => { this.writeDescription(writer, type); this.writeObjectShape(writer, type); }); writer.writeLine("})"); }); } mutationType(type) { this.sourcefile.addStatements((writer) => { writer.writeLine("builder.mutationType({"); writer.indent(() => { this.writeDescription(writer, type); this.writeObjectShape(writer, type); }); writer.writeLine("})"); }); } subscriptionType(type) { this.sourcefile.addStatements((writer) => { writer.writeLine("builder.subscriptionType({"); writer.indent(() => { this.writeDescription(writer, type); this.writeObjectShape(writer, type); }); writer.writeLine("})"); }); } objectType(type) { this.sourcefile.addStatements((writer) => { writer.writeLine(`builder.objectType('${type.name}', {`); writer.indent(() => { this.writeDescription(writer, type); if (type.getInterfaces().length > 0) { writer.writeLine(`interfaces: [${type .getInterfaces() .map((i) => i.name) .join(", ")}],`); writer.writeLine(`isTypeOf: (obj, context, info) => { throw new Error('Not implemented') },`); } this.writeObjectShape(writer, type); }); writer.writeLine("})"); }); } interfaceType(type) { this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, declarations: [ { kind: StructureKind.VariableDeclaration, name: type.name, initializer: (writer) => { writer.writeLine(`builder.interfaceType('${type.name}', {`); writer.indent(() => { this.writeDescription(writer, type); this.writeObjectShape(writer, type); }); writer.writeLine("})"); }, }, ], }); } unionType(type) { this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, declarations: [ { kind: StructureKind.VariableDeclaration, name: type.name, initializer: (writer) => { writer.writeLine(`builder.unionType('${type.name}', {`); writer.indent(() => { this.writeDescription(writer, type); writer.writeLine(`types: [${type.getTypes().map((t) => `'${t.name}'`)}],`); writer.writeLine(`resolveType: (parent, context, info) => throw new Error('Not implemented')`); }); writer.writeLine("})"); }, }, ], }); } scalarType(type) { this.sourcefile.addStatements((writer) => { writer.writeLine(`builder.scalarType('${type.name}', {`); writer.indent(() => { writer.writeLine(`serialize: () => { throw new Error('Not implemented') },`); }); writer.writeLine("})"); }); } inputType(type) { const recursive = isRecursive(type); if (recursive) { this.inputTypeShape(type); this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, declarations: [ { kind: StructureKind.VariableDeclaration, name: type.name, initializer: (writer) => { writer.writeLine(`builder.inputRef<${type.name}Shape>('${type.name}')`); }, }, ], }); this.sourcefile.addStatements((writer) => { writer.newLine(); writer.writeLine(`${type.name}.implement({`); writer.indent(() => { this.writeDescription(writer, type); this.writeInputShape(writer, type); }); writer.writeLine("})"); }); } else { this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, declarations: [ { kind: StructureKind.VariableDeclaration, name: type.name, initializer: (writer) => { writer.writeLine(`builder.inputType('${type.name}', {`); writer.indent(() => { this.writeDescription(writer, type); this.writeInputShape(writer, type); }); writer.writeLine("})"); }, }, ], }); } } inputTypeShape(type) { const fieldMap = type.getFields(); const fields = Object.keys(fieldMap).map((name) => fieldMap[name]); this.sourcefile.addInterface({ kind: StructureKind.Interface, name: `${type.name}Shape`, properties: fields.map((field) => ({ name: field.name, type: (writer) => void this.writeInputFieldShape(writer, field.type, type), })), }); } enumType(type) { this.sourcefile.addVariableStatement({ kind: StructureKind.VariableStatement, declarationKind: VariableDeclarationKind.Const, declarations: [ { kind: StructureKind.VariableDeclaration, name: type.name, initializer: (writer) => { writer.writeLine(`builder.enumType('${type.name}', {`); writer.indent(() => { this.writeDescription(writer, type); writer.writeLine("values: {"); writer.indent(() => { type.getValues().forEach((value) => { writer.writeLine(`${value.name}: {`); writer.indent(() => { this.writeDescription(writer, value); writer.write(`value: `); if (value.value) { writer.write(typeof value.value === "number" ? `${value.value} as const,` : `'${value.value}' as const,`); } else { writer.write(value.name); } writer.newLine(); }); writer.writeLine("},"); }); }); writer.writeLine("},"); }); writer.writeLine("})"); }, }, ], }); } writeObjectShape(writer, type) { const fieldMap = type.getFields(); const inheritedFields = type instanceof GraphQLObjectType ? type.getInterfaces().flatMap((i) => Object.keys(i.getFields())) : []; const fields = Object.keys(fieldMap).map((f) => fieldMap[f]); writer.writeLine("fields: t => ({"); writer.indent(() => { fields.forEach((field) => { if (inheritedFields.includes(field.name)) { return; } writer.writeLine(`${field.name}: t.field({`); writer.indent(() => { this.writeDescription(writer, field); this.writeArgs(writer, field); writer.write("type: "); this.writeType(writer, field.type); writer.writeLine(","); this.writeNullability(writer, field.type); writer.writeLine(`resolve: (parent, args, context, info) => { throw new Error('Not implemented') },`); if (type.name === "Subscription") { writer.writeLine(`subscribe: (parent, args, context, info) => { throw new Error('Not implemented') },`); } }); writer.writeLine("})"); }); }); writer.writeLine("}),"); } writeInputShape(writer, type) { writer.writeLine("fields: t => ({"); writer.indent(() => { const fieldMap = type.getFields(); const fields = Object.keys(fieldMap).map((name) => fieldMap[name]); fields.forEach((field) => { writer.writeLine(`${field.name}: t.field({`); writer.indent(() => { writer.write("type: "); this.writeType(writer, field.type); writer.write(","); writer.newLine(); this.writeDescription(writer, field); writer.write("required: "); this.writeRequiredness(writer, field.type); writer.write(","); writer.newLine(); }); writer.writeLine("}),"); }); }); writer.writeLine("}),"); } writeDescription(writer, type) { if (type.description) { writer.write("description:"); writer.quote(type.description); writer.writeLine(","); } } writeType(writer, type) { if (type instanceof GraphQLNonNull) { this.writeType(writer, type.ofType); return; } if (type instanceof GraphQLList) { writer.write("["); this.writeType(writer, type.ofType); writer.write("]"); return; } if (type instanceof GraphQLScalarType || type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) { writer.write(`'${type.name}'`); return; } writer.write(type.name); } writeInputFieldShape(writer, wrappedType, rootType) { const type = unwrap(wrappedType); if (wrappedType instanceof GraphQLNonNull && wrappedType.ofType instanceof GraphQLList) { this.writeInputFieldShape(writer, wrappedType.ofType.ofType, rootType); writer.write("[]"); } else if (wrappedType instanceof GraphQLList) { this.writeInputFieldShape(writer, wrappedType.ofType, rootType); writer.write("[]"); } else if (type instanceof GraphQLScalarType) { switch (type.name) { case "String": writer.write("string"); break; case "Int": writer.write("number"); break; case "Float": writer.write("number"); break; case "ID": writer.write("(string | number)"); break; case "Boolean": writer.write("boolean"); break; default: writer.write("unknown"); } } else if (type.name === rootType.name) { writer.write(`${rootType.name}Shape`); } else if (type instanceof GraphQLInputObjectType) { if (isRecursive(type)) { writer.write(`${rootType.name}Shape`); throw new Error(type.toString()); } else { writer.write("{"); writer.newLine(); writer.indent(() => { const fieldMap = type.getFields(); const fields = Object.keys(fieldMap).map((name) => fieldMap[name]); fields.forEach((field) => { writer.write(`${field.name}: `); this.writeInputFieldShape(writer, field.type, rootType); writer.write(";"); writer.newLine(); }); }); writer.newLine(); writer.write("}"); } } else if (type instanceof GraphQLEnumType) { writer.write(type .getValues() .map(({ value }) => (typeof value === "string" ? `'${value}'` : `${value}`)) .join(" | ")); } else { writer.write(rootType.name); } if (!(wrappedType instanceof GraphQLNonNull)) { writer.write("| null | undefined"); } } writeNullability(writer, type) { if (type instanceof GraphQLNonNull) { if (type.ofType instanceof GraphQLList && !(type.ofType.ofType instanceof GraphQLNonNull)) { writer.write("nullable: { list: false, items: true },"); } } else if (type instanceof GraphQLList) { if (type.ofType instanceof GraphQLNonNull) { writer.write("nullable: true,"); } else { writer.write(`nullable: { list: true, items: true },`); } } else { writer.write("nullable: true,"); } } writeRequiredness(writer, type) { if (type instanceof GraphQLNonNull) { if (type.ofType instanceof GraphQLList) { if (type.ofType.ofType instanceof GraphQLNonNull) { writer.write(`{ list: true, items: true }`); } else { writer.write(`{ list: true, items: false }`); } } else { writer.write("true"); } } else if (type instanceof GraphQLList) { if (type.ofType instanceof GraphQLNonNull) { writer.write(`{ list: false, items: true }`); } else { writer.write(`{ list: false, items: false }`); } } else { writer.write("false"); } } writeArgs(writer, type) { if (type.args.length > 0) { writer.write("args: {"); writer.indent(() => { type.args.forEach((arg) => { writer.writeLine(`${arg.name}: t.arg({`); writer.indent(() => { writer.write("type: "); this.writeType(writer, arg.type); writer.write(","); writer.newLine(); this.writeDescription(writer, arg); writer.write("required: "); this.writeRequiredness(writer, arg.type); writer.write(","); writer.newLine(); if (arg.defaultValue != null) { writer.write(`defaultValue: ${JSON.stringify(arg.defaultValue)}`); writer.write(","); writer.newLine(); } }); writer.newLine(); writer.writeLine("}),"); }); }); writer.writeLine("},"); } } writeTypeInfo(writer) { const typeMap = this.schema.getTypeMap(); const gqlTypes = Object.keys(typeMap) .map((typeName) => typeMap[typeName]) .filter((type) => !type.name.startsWith("__") && !builtins.includes(type.name)); writer.writeLine("Context: {}"); const objects = gqlTypes.filter((type) => type instanceof GraphQLObjectType && type.name !== "Query" && type.name !== "Mutation" && type.name !== "Subscription"); if (objects.length > 0) { writer.writeLine("Objects: {"); writer.indent(() => { objects.forEach((type) => { writer.writeLine(`${type.name}: unknown,`); }); }); writer.writeLine("},"); } const interfaces = gqlTypes.filter((type) => type instanceof GraphQLInterfaceType); if (interfaces.length > 0) { writer.writeLine("Interfaces: {"); writer.indent(() => { interfaces.forEach((type) => { writer.writeLine(`${type.name}: unknown,`); }); }); writer.writeLine("},"); } const scalars = gqlTypes.filter((type) => type instanceof GraphQLScalarType); if (scalars.length > 0) { writer.writeLine("Scalars: {"); writer.indent(() => { scalars.forEach((type) => { writer.writeLine(`${type.name}: { Input: unknown, Output: unknown },`); }); }); writer.writeLine("},"); } } toString() { return this.sourcefile.print(); } async saveAs(filePath) { await this.sourcefile.copyImmediately(filePath); } } //# sourceMappingURL=index.js.map