import { GraphQLSchema, IntrospectionType, introspectionFromSchema } from 'graphql'; import { concat, each, findIndex, get, includes, keys, mapKeys, omit, omitBy, set, startsWith } from 'lodash'; export default class SchemaInfoBuilder { private schema: GraphQLSchema; private schemaInfo: IntrospectionType[]; constructor(schema: GraphQLSchema) { this.schema = schema; } public getSchemaInfo(): IntrospectionType[] { if (!this.schemaInfo) { this.schemaInfo = this.buildSchemaInfo(this.schema); } return this.schemaInfo; } private addDirectiveFromAST(astDirective, schemaInfo, path) { const name = astDirective.name.value; const args = []; each(astDirective.arguments, arg => { args.push({ name: arg.name.value, ...omit(arg.value, ['loc']) }); }); const directives = get(schemaInfo, path) ? get(schemaInfo, path) : []; directives.push({ name: name, args: args }); set(schemaInfo, path, directives); } private buildSchemaInfo(schema): IntrospectionType[] { const originalSchemaInfo = introspectionFromSchema(schema, {descriptions: true}); let schemaInfo = originalSchemaInfo; schemaInfo = omitBy(schemaInfo.__schema.types, (value) => { return startsWith(value.name, '__') || includes(['Boolean', 'String', 'ID', 'Int', 'Float'], value.name); }); schemaInfo = mapKeys(schemaInfo, (type: any) => type.name); each(keys(schemaInfo), (typeName) => { const type = schemaInfo[typeName]; // directives on type each(get(schema.getType(typeName), 'astNode.directives'), (astDirective) => { this.addDirectiveFromAST(astDirective, schemaInfo, `${typeName}.directives`); }); // directives on fields each(get(schema.getType(typeName), 'astNode.fields'), (field) => { const fieldName = field.name.value; each(get(field, 'directives'), (astDirective) => { const fieldIndex = findIndex(get(schemaInfo, `${typeName}.fields`), { 'name': fieldName }); this.addDirectiveFromAST(astDirective, schemaInfo, `${typeName}.fields[${fieldIndex}].directives`); }); }); // metadata on type set(schemaInfo, `${typeName}.metadata`, omit(get(schema, `_typeMap.${typeName}`), ['astNode', 'name', 'description', 'extensionASTNodes', 'isTypeOf', '_fields', '_interfaces', '_typeConfig', 'getFields', 'getInterfaces', 'toString', 'inspect', 'toJSON', '_enumConfig', 'getValue', 'getValues', 'parseLiteral', 'parseValue', 'getValue', 'serialize', '_getNameLookup', '_getValueLookup', '_values', 'resolveType', 'getTypes', '_types'])); // metadata of fields each(get(schema, `_typeMap.${typeName}._fields`), (field) => { const fieldIndex = findIndex(get(schemaInfo, `${typeName}.fields`), { 'name': field.name }); set(schemaInfo, `${typeName}.fields[${fieldIndex}].metadata`, omit(field, ['type', 'description', 'args', 'deprecationReason', 'astNode', 'isDeprecated', 'name'])); }); // add unions to types if (type.kind === 'UNION') { each(type.possibleTypes, possibleType => { schemaInfo[possibleType.name].unions = schemaInfo[possibleType.name].unions ? schemaInfo[possibleType.name].unions : []; schemaInfo[possibleType.name].unions = concat(schemaInfo[possibleType.name].unions, [{ kind: type.kind, name: type.name, ofType: type.ofType }]); }); } }); return schemaInfo; } }