UNPKG

@notadd/graphql

Version:

notadd core none dependence

379 lines (377 loc) 16.5 kB
import { Runable, Router, Injector, Injectable, ARGS, GET_INGER_DECORATOR, ProxyHandler, METHOD_RESULT, Middleware } from '@notadd/core'; import { GRAPHQL_SELECTION_SET, GRAPHQL_SELECTIONS, GRAPHQL_SELECTION, GRAPHQL_OBJECT_TYPE, GRAPHQL_SCHEMA, GRAPHQL_SOURCE } from './token'; import { ValueNode } from './core'; import { EnumValueNode, ListValueNode, ObjectValueNode, VariableNode, IntValueNode, FloatValueNode, StringValueNode, BooleanValueNode, NullValueNode, ArgumentNode, FieldNode, FragmentSpreadNode, InlineFragmentNode, SelectionNode, NameNode, SelectionSetNode, GraphQLError, GraphQLObjectType, getOperationAST, getOperationRootType, DocumentNode, FragmentDefinitionNode, graphql } from 'graphql'; import { isObservable } from 'rxjs'; import { map } from 'rxjs/operators' import { parse } from 'graphql' import { DefaultSchemaBuilder } from './schemaBuilder'; @Injectable() export class Graphql implements Runable { constructor(private injector: Injector) { } async query(source: string, variables: any, operationName: string | undefined = undefined, ...middlewars: Middleware[]) { const builder = this.injector.get(DefaultSchemaBuilder) const schema = await builder.buildSchema(); if (schema) { return graphql({ schema, source, rootValue: undefined, contextValue: middlewars, variableValues: variables, operationName }) } } async run<T>(query: string, variables: any, operationName?: string, ...middlewars: Middleware[]): Promise<T | undefined> { this.ast = parse(query) const schema = await this.injector.get(GRAPHQL_SCHEMA) const operationDefinition = getOperationAST(this.ast, operationName) if (operationDefinition) { const rootType = getOperationRootType(schema, operationDefinition) const selectionSet = operationDefinition.selectionSet; const method = rootType.name; const source = await this.handlerSelectionSet(middlewars, rootType, selectionSet, variables, method) return source; } } ast: DocumentNode; private getFragmentDefinition(name: string): FragmentDefinitionNode | undefined { return this.ast.definitions.find((it: any) => it.name.value === name) as any; } private handlerSelectionSet(middlewars: Middleware[], root: GraphQLObjectType, set: SelectionSetNode, variables: any, method: string) { middlewars.push(new Middleware('graphql', async (root: Injector | Promise<Injector>) => { const injector = await root; return injector.create([{ provide: GRAPHQL_OBJECT_TYPE, useValue: root }, { provide: GRAPHQL_SELECTION_SET, useValue: set }, { provide: ARGS, useValue: variables }, { provide: GRAPHQL_SELECTIONS, useFactory: () => { if (set) { return set.selections.map(it => { if (isFieldNode(it)) { return it.name.value } return; }).filter(it => !!it) } return [] } }], 'graphql') })) if (set.selections && set.selections.length === 1) { const selection = set.selections[0]; return this.handlerField(middlewars, selection, set, variables, method) } throw new GraphQLError(`subscription support only one top selection`) } private async handlerField(middlewars: Middleware[], field: SelectionNode, set: SelectionSetNode, variables: any, method: string) { if (isFieldNode(field)) { let { alias, name, arguments: args, directives, selectionSet } = field; alias = alias || name; const router = this.injector.get(Router) const [handler] = router.find(`/${name.value}`, method.toUpperCase()) if (handler) { const graphqlMiddlware = new Middleware('selection', async (_root: Injector | Promise<Injector>) => { const injector = await _root; return injector.create([{ provide: GRAPHQL_SELECTION_SET, useValue: selectionSet }, { provide: GRAPHQL_SELECTION, useValue: field }, { provide: GRAPHQL_SELECTIONS, useFactory: () => { if (selectionSet) { return selectionSet.selections.map(it => { if (isFieldNode(it)) { return it.name.value } return; }).filter(it => !!it) } return [] } }], 'selection') }) middlewars.push(graphqlMiddlware) let res = await handler(middlewars); if (res instanceof Injector) { res = res.get(METHOD_RESULT) } if (selectionSet) { return this.parseSelectionSetFirst(middlewars, res, alias, name, selectionSet, variables) } else { if (isPromise(res)) { return res.then(item => { return { [alias!.value]: item }; }) } return { [alias.value]: res }; } } } else if (isFragmentSpreadNode(field)) { debugger; } else if (isInlineFragmentNode(field)) { debugger; } else { throw new Error(`not support field type`) } } private parseSelectionSetFirst(middlewars: Middleware[], source: any, alias: NameNode, name: NameNode, selectionSet?: SelectionSetNode, variables?: any) { if (selectionSet) { middlewars.push(new Middleware('selection', async (root: Injector | Promise<Injector>) => { const injector = await root; return injector.create([{ provide: GRAPHQL_SELECTION_SET, useValue: selectionSet }, { provide: GRAPHQL_SELECTIONS, useFactory: () => { return selectionSet.selections.map(it => { if (isFieldNode(it)) { return it.name.value } return; }).filter(it => !!it) } }], 'selection') })) const selections = selectionSet.selections if (selections) { if (isObservable(source)) { return source.pipe( map((item: any) => { return { [alias.value]: this.parseSelectionSet(item, selectionSet, variables) } }) ) } else if (isPromise(source)) { return source.then(res => ({ [alias.value]: this.parseSelectionSet(res, selectionSet, variables) })) } else if (Array.isArray(source)) { return { [alias.value]: source.map(it => this.parseSelectionSet(it, selectionSet, variables)) } } else if (source !== null && source !== undefined) { if (selections && selections.length > 0) { let res = {}; selections.map(it => { const val = this.parseField(source, it, variables) res = { ...res, ...val } }) return { [alias.value]: res } } return { [alias.value]: source }; } else { return { [alias.value]: null } } } } return source; } private parseValue(value: ValueNode, variables: any): any { if (isVariableNode(value)) { return Reflect.get(variables, value.name.value) } else if (isIntValueNode(value)) { return parseInt(value.value, 10) } else if (isFloatValueNode(value)) { return parseFloat(value.value) } else if (isStringValueNode(value)) { return `${value.value}` } else if (isBooleanValueNode(value)) { return value.value } if (isNullValueNode(value)) { return null; } else if (isEnumValueNode(value)) { console.log(`enum value`) return value.value } else if (isListValueNode(value)) { return value.values.map(it => this.parseValue(it, variables)) } else if (isObjectValueNode(value)) { const res = {}; value.fields.map(it => { Reflect.set(res, it.name.value, this.parseValue(it.value, variables)) }) return res; } else { throw new Error(`not support value type`) } } private parseField(source: any, selection: SelectionNode, variables?: any) { if (isFieldNode(selection)) { let { alias, name, arguments: args, directives, selectionSet } = selection; let value: any = source[name.value]; alias = alias || name; const parameters = {} const getNger = this.injector.get(GET_INGER_DECORATOR) const type = source.constructor; const nger = getNger(type) const method = nger.methods.find(it => it.property === name.value) const _args = (args || []).map(arg => { const { name, value } = arg; const val = this.parseValue(value, variables) Reflect.set(parameters, name.value, val) return val; }) const argsInjector = this.injector.create([{ provide: GRAPHQL_SOURCE, useValue: source }, { provide: ARGS, useValue: parameters }, { provide: GRAPHQL_SELECTION_SET, useValue: selectionSet }, { provide: GRAPHQL_SELECTIONS, useFactory: () => { if (selectionSet) { return selectionSet.selections.map(it => { if (isFieldNode(it)) { return it.name.value } return; }).filter(it => !!it) } return []; } }], 'field') if (method) { const args = new Array(method.paramTypes) nger.properties.map(pro => { if (pro.metadataKey) { const handler = argsInjector.get<ProxyHandler | null>(pro.metadataKey, null) if (handler) { handler.property(pro, argsInjector) } } }) method.parameters.map(par => { if (par.metadataKey) { const handler = argsInjector.get<ProxyHandler<any, any> | null>(par.metadataKey, null) if (handler) { const value = handler.methodParams(args, par, argsInjector) Reflect.set(args, par.parameterIndex, value) } } }) value = value(...args) } else { if (typeof value === 'function') { value = value(..._args) } } if (selectionSet) { const res = { [alias.value]: this.parseSelectionSet(value, selectionSet, variables) } return res; } else { return { [alias.value]: value }; } } else if (isFragmentSpreadNode(selection)) { const fragment = this.getFragmentDefinition(selection.name.value) if (fragment) { return this.parseSelectionSet(source, fragment.selectionSet, variables) } else { throw new Error(`not found fragment ${selection.name.value}`) } } else if (isInlineFragmentNode(selection)) { debugger; } else { throw new Error(`not support field type`) } } private parseSelectionSet(source: any, selectionSet?: SelectionSetNode, variables?: any): any { if (selectionSet) { const selections = selectionSet.selections if (selections) { if (isObservable(source)) { return source.pipe( map(item => { return this.parseSelectionSet(item, selectionSet, variables) }) ) } else if (isPromise(source)) { return source.then(res => this.parseSelectionSet(res, selectionSet, variables)) } else if (Array.isArray(source)) { return source.map(it => this.parseSelectionSet(it, selectionSet, variables)) } else if (source !== null && source !== undefined) { if (selections && selections.length > 0) { let res = {}; selections.map(it => { const val = this.parseField(source, it, variables) res = { ...res, ...val } }) return res } return source; } else { return null } } } return source; } } export function isEnumValueNode(val: any): val is EnumValueNode { return Reflect.get(val, 'kind') === 'EnumValue' } export function isListValueNode(val: any): val is ListValueNode { return Reflect.get(val, 'kind') === 'ListValue' } export function isObjectValueNode(val: any): val is ObjectValueNode { return Reflect.get(val, 'kind') === 'ObjectValue' } export function isVariableNode(val: any): val is VariableNode { return Reflect.get(val, 'kind') === 'Variable' } export function isIntValueNode(val: any): val is IntValueNode { return Reflect.get(val, 'kind') === 'IntValue' } export function isFloatValueNode(val: any): val is FloatValueNode { return Reflect.get(val, 'kind') === 'FloatValue' } export function isStringValueNode(val: any): val is StringValueNode { return Reflect.get(val, 'kind') === 'StringValue' } export function isBooleanValueNode(val: any): val is BooleanValueNode { return Reflect.get(val, 'kind') === 'BooleanValue' } export function isNullValueNode(val: any): val is NullValueNode { return Reflect.get(val, 'kind') === 'NullValue' } export function isArgumentNode(val: any): val is ArgumentNode { return Reflect.get(val, 'kind') === 'Argument' } export function isFieldNode(val: any): val is FieldNode { return Reflect.get(val, 'kind') === 'Field' } export function isFragmentSpreadNode(val: any): val is FragmentSpreadNode { return Reflect.get(val, 'kind') === 'FragmentSpread' } export function isInlineFragmentNode(val: any): val is InlineFragmentNode { return Reflect.get(val, 'kind') === 'InlineFragment' } export function isPromise(val: any): val is Promise<any> { return val && typeof val === 'object' && Reflect.has(val, 'then') && Reflect.has(val, 'catch') }