UNPKG

@notadd/graphql

Version:

notadd core none dependence

145 lines (143 loc) 5.62 kB
import { Injectable, Injector, MAIN_PATH, PLATFORM_NAME, GRAPHQL_FIELDS, isDevMode } from '@notadd/core'; import { DocumentNode, GraphQLSchema, parse, introspectionFromSchema, IntrospectionQuery, ObjectTypeDefinitionNode, DefinitionNode, FieldDefinitionNode } from "graphql"; import { dirname, extname, join } from 'path'; import { Fs } from '@notadd/platform'; import { RESOLVER } from './token'; import { makeExecutableSchema } from 'graphql-tools'; export async function getTsconfig(path: string, existsSync: Function): Promise<string> { const dir = join(path, 'tsconfig.json') if (await existsSync(dir) || await existsSync(join(path, 'package.json'))) return dir; return getTsconfig(join(path, '..'), existsSync) } @Injectable() export class DefaultSchemaBuilder { _schema: GraphQLSchema; _node: string | undefined; _introspection: IntrospectionQuery; constructor(public injector: Injector) { } async buildDocument(): Promise<string | undefined> { try { if (this._node) return this._node; const mainPath = this.injector.get<string>(MAIN_PATH) const fs = this.injector.get(Fs) const ext = extname(mainPath) const graphqlPath = mainPath.replace(ext, '.graphql') const platformName = this.injector.get(PLATFORM_NAME, 'core') if (!isDevMode()) { const buffer = await fs.readFile(graphqlPath) this._node = buffer.toString('utf-8') } else { const toGraphql = require('@notadd/ast.ts-graphql').toGraphql const tsconfig = await getTsconfig(dirname(mainPath), fs.exists) const graphql = toGraphql(mainPath, tsconfig, platformName); await fs.writeFile(graphqlPath, graphql.graphql) this._node = graphql.graphql } return this._node; } catch (e) { console.log(`build document`, e.message) } } async buildSchema(): Promise<GraphQLSchema | undefined> { if (this._schema) return this._schema; const ast = await this.buildDocument(); if (ast) { const fields = await this.injector.get(GRAPHQL_FIELDS, Promise.resolve([])) const resolver = this.injector.get(RESOLVER) fields.push(ast) const _ast = fields.map(it => parse(it)).reduce((a: DocumentNode, b: DocumentNode) => { if (a) { return this.mergeDocumentNode(a, b) } return b; }, null as any); const schema = makeExecutableSchema({ typeDefs: [ _ast ], resolvers: resolver }); this._schema = schema return this._schema; } } mergeObjectTypeDefinitionNode(a: ObjectTypeDefinitionNode, b: ObjectTypeDefinitionNode) { if (!b.fields) { return a; } if (b.fields.length === 0) { return a; } if (!a.fields) { return b; } if (a.fields.length === 0) { return b; } const fields: FieldDefinitionNode[] = [] a.fields.forEach(afield => { const same = fields.find(bfield => afield.name.value === bfield.name.value) if (!same) { fields.push(afield) } }) b.fields.forEach(afield => { const same = fields.find(bfield => afield.name.value === bfield.name.value) if (!same) { fields.push(afield) } }) return { ...a, fields } } mergeDocumentNode(a: DocumentNode, b: DocumentNode): DocumentNode { const definitions: DefinitionNode[] = []; if (b.definitions.length === 0) { return a; } else if (a.definitions.length === 0) { return b; } else { b.definitions.forEach((bit: any) => { const same = definitions.find((ait: any) => ait.name.value === bit.name.value) if (same) { if (isObjectTypeDefinitionNode(same)) { definitions.splice(definitions.indexOf(same), 1) definitions.push(this.mergeObjectTypeDefinitionNode(same, bit)) } } else { definitions.push(bit) } }) a.definitions.forEach((bit: any) => { const same = definitions.find((ait: any) => ait.name.value === bit.name.value) if (same) { if (isObjectTypeDefinitionNode(same)) { definitions.splice(definitions.indexOf(same), 1) definitions.push(this.mergeObjectTypeDefinitionNode(same, bit)) } } else { definitions.push(bit) } }) } return { kind: 'Document', loc: undefined, definitions } } async buildIntrospection() { if (this._introspection) return this._introspection const schema = await this.buildSchema() if (schema) { this._introspection = introspectionFromSchema(schema) return this._introspection; } } } export function isObjectTypeDefinitionNode(val: any): val is ObjectTypeDefinitionNode { return Reflect.get(val, 'kind') === 'ObjectTypeDefinition' }