@notadd/graphql
Version:
notadd core none dependence
379 lines (377 loc) • 16.5 kB
text/typescript
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';
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')
}