@notadd/graphql
Version:
notadd core none dependence
145 lines (143 loc) • 5.62 kB
text/typescript
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)
}
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'
}