import { GraphQLObjectType, GraphQLInt, GraphQLInputObjectType, GraphQLList, GraphQLFieldConfigMap, GraphQLFieldConfigArgumentMap, GraphQLBoolean, GraphQLFieldResolver, GraphQLFieldConfig, GraphQLNonNull, Thunk, GraphQLObjectTypeConfig, GraphQLInputObjectTypeConfig, GraphQLResolveInfo, GraphQLEnumType, GraphQLEnumTypeConfig, GraphQLEnumValueConfigMap, GraphQLString, } from "graphql"; import CONS from "./constant"; import _ from "lodash"; import {map2NullableType} from "./utils"; import { FilterActions, BaseFieldConfig, BaseConfig, BaseAdapterInterface, BaseHook, Query, Mutation, MaybePromise, Resolver } from "./BaseAdapterType"; export * from "./BaseAdapterType"; import {withFilter} from "graphql-subscriptions"; import {BasicType, OrderSortEnum, SubscriptionTriggerEnum} from "./sequelizeImpl"; /** * 类型容器 * @type {Map | GraphQLInputObjectType>} */ export const typeMap = new Map(); type WithNameType = GraphQLObjectType | GraphQLInputObjectType | GraphQLEnumType /** * * @param config * @returns {GraphQLObjectType | GraphQLInputObjectType | GraphQLEnumType} * @param type */ export function getCacheGraphqlType | GraphQLInputObjectTypeConfig | GraphQLEnumTypeConfig>(config: TType, type: any = GraphQLObjectType): WithNameType { if (!typeMap.has(config.name)) { typeMap.set(config.name, new type(config)); } return typeMap.get(config.name); } function thunkGet(value: Thunk): T { if (_.isFunction(value)) return value(); return value; } /** * 通过配置判断是否过滤字段 * @param include * @param exclude * @param key * @returns {boolean} */ function filterAction(include?: boolean | FilterActions, exclude?: FilterActions, key?: T): boolean { if (include === false) return false; if (!_.isUndefined(exclude)) { if (_.isFunction(exclude) && exclude(key)) return false; if (_.isArray(exclude) && exclude.includes(key)) return false; } if (include) { if (include === true) return true; if (_.isFunction(include) && !include(key)) return false; if (_.isArray(include) && !include.includes(key)) return false; } return true; } function getFieldName(defaultName: string, config?: BaseFieldConfig) { return config?.name || defaultName; } export abstract class BaseAdapter, TArgs, TConfig extends BaseConfig> implements BaseAdapterInterface { config: TConfig; abstract createFields?: GraphQLFieldConfigArgumentMap; abstract associationsFields?: GraphQLFieldConfigMap; abstract associationsUpdateFields?: GraphQLFieldConfigArgumentMap; abstract associationsCreateFields?: GraphQLFieldConfigArgumentMap; abstract modelFields?: GraphQLFieldConfigMap; abstract customFields?: GraphQLFieldConfigMap; abstract updateFields?: GraphQLFieldConfigArgumentMap; abstract inputArgs: GraphQLFieldConfigArgumentMap; abstract inputListArgs: GraphQLFieldConfigArgumentMap; abstract subscriptionFilterArgs: GraphQLFieldConfigArgumentMap; abstract aggregateArgs: GraphQLFieldConfigArgumentMap; primaryKey: GraphQLFieldConfigArgumentMap; primaryKeyName?: string; description?: string; protected constructor(config: TConfig) { this.config = config; this.primaryKey = this.config.primaryKey || {}; this.primaryKeyName = Object.keys(this.primaryKey)[0]; this.description = thunkGet(this.config.description) || this.name; } private _applyMiddleware({before, after}: BaseHook): Resolver { return (fn: GraphQLFieldResolver) => async (source, args, ctx, info) => { if (_.isFunction(this.config.before)) { args = await (this.config.before)(source, args, ctx, info); } if (!_.isUndefined(before)) { args = await before(source, args, ctx, info); } let response = await fn(source, args, ctx, info); if (!_.isUndefined(after)) { response = await after(response, source, args, ctx, info); } if (_.isFunction(this.config.after)) { return this.config.after(response, source, args, ctx, info); } return response; }; } /** * 模型类型 * @returns {GraphQLObjectType} */ get modelType(): GraphQLObjectType { const {handleModelFields = v => v} = this.config; return getCacheGraphqlType>({ name: this.upperName, description: this.description, ...(this.config.modelTypeConfig || {}), fields: () => handleModelFields({ ...this.modelFields, ...this.customFields, ...this.associationsFields, ...thunkGet(this.config.associationsFields || {}), ...thunkGet(this.config.modelFields || {}) }) }) as GraphQLObjectType; } /** * 模型字段枚举 * @returns {GraphQLObjectType} */ get fieldEnumType(): GraphQLEnumType { return getCacheGraphqlType({ name: `${this.upperName}FieldEnum`, values: Object.keys(this.modelFields).filter(t => ![CONS.colName, CONS.aggregationName].includes(t)).reduce((memo, key) => { memo[key] = {value: key, description: this.modelFields[key].description}; return memo; }, {[CONS.fieldAllMenu]: {value: "*", description: "all"}}) }, GraphQLEnumType) as GraphQLEnumType; } /** * 排序类型 * @returns {GraphQLInputObjectType} */ get orderType(): GraphQLInputObjectType { return getCacheGraphqlType({ name: `${this.upperName}OrderType`, fields: { name: { description: "sort field", type: GraphQLNonNull(GraphQLString) }, sort: { description: "sort type", type: OrderSortEnum } } }, GraphQLInputObjectType) as GraphQLInputObjectType; } /** * 分页数据类型 * @returns {GraphQLObjectType} */ get pageType(): GraphQLObjectType { return getCacheGraphqlType({ name: `${this.upperName}Page`, fields: () => ({ count: { description: "total number", type: GraphQLInt }, rows: { description: "列表数据", type: new GraphQLList(this.modelType) }, }) }) as GraphQLObjectType; } /** * 更新参数类型 * @returns {GraphQLInputObjectType} */ get updateType(): GraphQLInputObjectType { const {handleUpdateTypeFields = v => v} = this.config; return getCacheGraphqlType({ name: `${this.upperName}UpdateInput`, ...(this.config.updateTypeConfig || {}), fields: () => handleUpdateTypeFields({ ...map2NullableType(this.updateFields), ...this.associationsUpdateFields, ...thunkGet(this.config.updateTypeConfig?.fields || {}), ...thunkGet(this.config.associationsUpdateFields || {}), }) }, GraphQLInputObjectType) as GraphQLInputObjectType; } /** * 创建参数类型 * @returns {GraphQLInputObjectType} */ get createType(): GraphQLInputObjectType { const {handleCreateTypeFields = v => v} = this.config; return getCacheGraphqlType({ name: `${this.upperName}CreateInput`, ...this.config.createTypeConfig, fields: () => handleCreateTypeFields({ ...this.createFields, ...this.associationsCreateFields, ...thunkGet(this.config.createTypeConfig?.fields || {}), ...thunkGet(this.config.associationsCreateFields || {}), }) }, GraphQLInputObjectType) as GraphQLInputObjectType; } get name() { return thunkGet(this.config.name); } private _getFieldConfig(defaultConfig: GraphQLFieldConfig, config: BaseFieldConfig = {}) { const {before, after, resolve, ...otherConfig} = config; return { ...defaultConfig, ...otherConfig, resolve: this._applyMiddleware({before, after})(resolve || defaultConfig.resolve) }; } /** * 查询单个数据 * @returns {GraphQLFieldConfig & Pick & BaseHook, TSource, TArgs, TContext> & {name?: string}, keyof GraphQLFieldConfigOptions & BaseHook, TSource, TArgs, TContext> & {name?: string} extends ("after" | "resolve" | "before") ? never : keyof GraphQLFieldConfigOptions & BaseHook, TSource, TArgs, TContext> & {name?: string}> & {resolve: (source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo) => any}} */ get getOne() { return this._getFieldConfig({ type: this.modelType, description: `fetch one ${this.description}`, args: { ...this.inputArgs, ...(this.config.getOne?.args || {}), }, resolve: this.getOneResolve.bind(this) }, this.config.getOne); } /** * 获取列表数据 * @returns {GraphQLFieldConfig & Pick & BaseHook, TSource, TArgs, TContext> & {name?: string}, keyof GraphQLFieldConfigOptions & BaseHook, TSource, TArgs, TContext> & {name?: string} extends ("after" | "resolve" | "before") ? never : keyof GraphQLFieldConfigOptions & BaseHook, TSource, TArgs, TContext> & {name?: string}> & {resolve: (source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo) => any}} */ get getList() { return this._getFieldConfig({ type: new GraphQLList(this.modelType), description: `fetch ${this.description} list`, args: { ...this.inputListArgs, ...(this.config.getList?.args || {}) }, resolve: this.getListResolve.bind(this) }, this.config.getList); } /** * 获取聚合数据 * @returns {GraphQLFieldConfig & Pick & BaseHook, TSource, TArgs, TContext> & {name?: string}, keyof GraphQLFieldConfigOptions & BaseHook, TSource, TArgs, TContext> & {name?: string} extends ("after" | "resolve" | "before") ? never : keyof GraphQLFieldConfigOptions & BaseHook, TSource, TArgs, TContext> & {name?: string}> & {resolve: (source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo) => any}} */ get getAggregate() { return this._getFieldConfig({ type: BasicType, description: `${this.description}聚合方法`, args: { ...this.aggregateArgs, ...(this.config.getAggregate?.args || {}) }, resolve: this.getAggregateResolve.bind(this) }, this.config.getAggregate); } /** * 新建mutation * @returns {GraphQLFieldConfig & Pick & BaseHook & {name?: string}, keyof GraphQLFieldConfig & BaseHook & {name?: string} extends "after" | "resolve" | "before" ? never : keyof GraphQLFieldConfig & BaseHook & {name?: string}> & {resolve: GraphQLFieldResolver}} */ get create() { return this._getFieldConfig({ type: this.modelType, resolve: this.createResolve.bind(this), description: `create ${this.description}`, args: { data: { description: "create data", type: new GraphQLNonNull(this.createType) }, } }, this.config.create); } /** * 更新mutation * @returns {GraphQLFieldConfig & Pick & BaseHook & {name?: string}, keyof GraphQLFieldConfig & BaseHook & {name?: string} extends "after" | "resolve" | "before" ? never : keyof GraphQLFieldConfig & BaseHook & {name?: string}> & {resolve: GraphQLFieldResolver}} */ get update() { return this._getFieldConfig({ type: this.modelType, description: `update ${this.description}`, resolve: this.updateResolve.bind(this), args: { data: { description: "update data", type: new GraphQLNonNull(this.updateType) }, ...this.primaryKey } }, this.config.update); } /** * 删除mutation * @returns {GraphQLFieldConfig & Pick & BaseHook & {name?: string}, keyof GraphQLFieldConfig & BaseHook & {name?: string} extends "after" | "resolve" | "before" ? never : keyof GraphQLFieldConfig & BaseHook & {name?: string}> & {resolve: GraphQLFieldResolver}} */ get remove() { return this._getFieldConfig({ type: GraphQLBoolean, description: `remove ${this.description}`, resolve: this.removeResolve.bind(this), args: { ...this.primaryKey } }, this.config.remove); } get upperName() { return _.upperFirst(this.name); } get nameQueryOne() { return getFieldName(CONS.getOneField, this.config.getOne); } get nameQueryList() { return getFieldName(CONS.getListField, this.config.getList); } get nameQueryAggregate() { return getFieldName(CONS.getAggregateField, this.config.getAggregate); } /** * 所有query的字段 * @returns {{} | {[p: string]: {resolve(): {}, description: string, type: GraphQLObjectType}}} */ get queryFields(): GraphQLFieldConfigMap { const {includeQuery = true, excludeQuery, queryFields} = this.config; if (!includeQuery) return {}; const fields = [Query.ONE, Query.LIST, Query.AGGREGATE].reduce> ((memo, key) => { if (!filterAction(includeQuery, excludeQuery, key)) return memo; if (key === Query.ONE) { memo[this.nameQueryOne] = this.getOne; } else if (key === Query.LIST) { memo[this.nameQueryList] = this.getList; } else if (key === Query.AGGREGATE) { memo[this.nameQueryAggregate] = this.getAggregate; } return memo; }, {}); return { [this.name]: { description: this.description, resolve() { return {}; }, type: new GraphQLObjectType({ name: `${this.upperName}Query`, fields: { ...fields, ...thunkGet(queryFields || {}) } }) } }; } get nameMutationCreate() { return getFieldName("create", this.config.create); } get nameMutationRemove() { return getFieldName("remove", this.config.remove); } get nameMutationUpdate() { return getFieldName("update", this.config.update); } /** * 所有mutation的字段 * @returns {{} | {[p: string]: {resolve(): {}, description: string, type: GraphQLObjectType}}} */ get mutationFields(): GraphQLFieldConfigMap { const {includeMutation = true, excludeMutation, mutationFields} = this.config; if (!includeMutation) return {}; const fields = [Mutation.CREATE, Mutation.UPDATE, Mutation.REMOVE].reduce> ((memo, key) => { if (!filterAction(includeMutation, excludeMutation, key)) return memo; if (key === Mutation.CREATE) { memo[this.nameMutationCreate] = this.create; } else if (key === Mutation.REMOVE && this.primaryKeyName) { memo[this.nameMutationRemove] = this.remove; } else if (key === Mutation.UPDATE && this.primaryKeyName) { memo[this.nameMutationUpdate] = this.update; } return memo; }, {}); return { [this.name]: { description: this.description, resolve() { return {}; }, type: new GraphQLObjectType({ name: `${this.upperName}Mutation`, fields: { ...fields, ...thunkGet(mutationFields || {}) }, }) } }; } get eventName() { return `${this.upperName}Event`; } /** * 创建的事件 * @returns {string} */ get createdEvent() { return `${this.eventName}.Created`; } /** * 删除的事件 * @returns {string} */ get removedEvent() { return `${this.eventName}.Removed`; } /** * 更新的事件 * @returns {string} */ get updatedEvent() { return `${this.eventName}.Updated`; } /** * 所有监听的字段 * @returns {{} | {[p: string]: {args: {[p: string]: GraphQLArgumentConfig, triggerType: {description: string, type: GraphQLNonNull}}, subscribe: (rootValue?: any, args?: any, context?: any, info?: any) => AsyncIterator, description: string, type: GraphQLObjectType}}} */ get subscriptionFields(): GraphQLFieldConfigMap { const defaultFilter = (source: any, args: object) => _.isMatch(source[this.name] as object, _.omit(args, "triggerType")); const { includeSubscription = true, pubSub, filterSubscription = defaultFilter } = this.config; if (_.isUndefined(pubSub) || !includeSubscription) return {}; return { [this.name]: { description: this.description, args: { ...this.subscriptionFilterArgs, triggerType: { description: "触发类型", type: GraphQLNonNull(SubscriptionTriggerEnum) } }, subscribe: withFilter((source, {triggerType}) => pubSub?.asyncIterator(`${this.eventName}.${triggerType}`), filterSubscription), type: this.modelType } }; } /** * 实现查询单个的方法 * @param source * @param args * @param context * @param info */ abstract getOneResolve(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo): MaybePromise; /** * 实现聚合函数的方法 * @param source * @param args * @param context * @param info */ abstract getAggregateResolve(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo): MaybePromise; /** * 实现获取列表的方法 * @param source * @param args * @param context * @param info */ abstract getListResolve(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo): MaybePromise /** * 实现删除的方法 * @param source * @param args * @param context * @param info */ abstract removeResolve(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo): MaybePromise; /** * 实现新建的方法 * @param source * @param args * @param context * @param info */ abstract createResolve(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo): MaybePromise; /** * 实现更新的方法 * @param source * @param args * @param context * @param info */ abstract updateResolve(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo): MaybePromise; }