import { type FieldKind, type FieldRef, type InputFieldMap, isThenable, type MaybePromise, ObjectRef, PothosError, RootFieldBuilder, type SchemaTypes, } from '@pothos/core'; import { type GraphQLResolveInfo, getNamedType, isInterfaceType, isObjectType, Kind, } from 'graphql'; import { ModelLoader } from './model-loader'; import type { PrismaConnectionFieldOptions, PrismaModelTypes } from './types'; import { getCursorFormatter, getCursorParser, resolvePrismaCursorConnection } from './util/cursors'; import { getRefFromModel } from './util/datamodel'; import { queryFromInfo } from './util/map-query'; import { isUsed } from './util/usage'; const fieldBuilderProto = RootFieldBuilder.prototype as PothosSchemaTypes.RootFieldBuilder< SchemaTypes, unknown, FieldKind >; fieldBuilderProto.prismaField = function prismaField({ type, resolve, ...options }) { const modelOrRef = Array.isArray(type) ? type[0] : type; const typeRef = typeof modelOrRef === 'string' ? getRefFromModel(modelOrRef, this.builder) : (modelOrRef as ObjectRef); const typeParam = Array.isArray(type) ? ([typeRef] as [ObjectRef]) : typeRef; return this.field({ ...(options as {}), type: typeParam, resolve: (parent: never, args: unknown, context: {}, info: GraphQLResolveInfo) => { const query = queryFromInfo({ context, info, withUsageCheck: !!this.builder.options.prisma?.onUnusedQuery, skipDeferredFragments: this.builder.options.prisma?.skipDeferredFragments, }); return checkIfQueryIsUsed( this.builder, query, info, resolve(query, parent, args as never, context, info) as never, ); }, }) as never; }; fieldBuilderProto.prismaFieldWithInput = function prismaFieldWithInput( this: typeof fieldBuilderProto, { type, resolve, ...options }: { type: ObjectRef | [string]; resolve: (...args: unknown[]) => unknown }, ) { const modelOrRef = Array.isArray(type) ? type[0] : type; const typeRef = typeof modelOrRef === 'string' ? getRefFromModel(modelOrRef, this.builder) : (modelOrRef as ObjectRef); const typeParam = Array.isArray(type) ? ([typeRef] as [ObjectRef]) : typeRef; return ( this as typeof fieldBuilderProto & { fieldWithInput: typeof fieldBuilderProto.field } ).fieldWithInput({ ...(options as {}), type: typeParam, resolve: (parent: never, args: unknown, context: {}, info: GraphQLResolveInfo) => { const query = queryFromInfo({ context, info, withUsageCheck: !!this.builder.options.prisma?.onUnusedQuery, skipDeferredFragments: this.builder.options.prisma?.skipDeferredFragments, }); return checkIfQueryIsUsed( this.builder, query, info, resolve(query, parent, args as never, context, info) as never, ); }, }) as never; } as never; fieldBuilderProto.prismaConnection = function prismaConnection< Type extends keyof SchemaTypes['PrismaTypes'], Nullable extends boolean, ResolveReturnShape, Args extends InputFieldMap, Model extends PrismaModelTypes, >( this: typeof fieldBuilderProto, { type, cursor, maxSize = this.builder.options.prisma?.maxConnectionSize, defaultSize = this.builder.options.prisma?.defaultConnectionSize, resolve, totalCount, ...options }: PrismaConnectionFieldOptions< SchemaTypes, unknown, Type, Model, ObjectRef, Nullable, Args, ResolveReturnShape, FieldKind >, connectionOptions: {} = {}, edgeOptions: {} = {}, ) { const ref = typeof type === 'string' ? getRefFromModel(type, this.builder) : type; const typeName = this.builder.configStore.getTypeConfig(ref).name; const model = this.builder.configStore.getTypeConfig(ref).extensions?.pothosPrismaModel as string; const formatCursor = getCursorFormatter(model, this.builder, cursor); const parseCursor = getCursorParser(model, this.builder, cursor); const cursorSelection = ModelLoader.getCursorSelection(ref, model, cursor, this.builder); const fieldRef = ( this as typeof fieldBuilderProto & { connection: (...args: unknown[]) => FieldRef; } ).connection( { ...options, type: ref, resolve: ( parent: unknown, args: PothosSchemaTypes.DefaultConnectionArguments, context: {}, info: GraphQLResolveInfo, ) => { const query = queryFromInfo({ context, info, select: cursorSelection as {}, paths: [['nodes'], ['edges', 'node']], typeName, withUsageCheck: !!this.builder.options.prisma?.onUnusedQuery, skipDeferredFragments: this.builder.options.prisma?.skipDeferredFragments, }); const returnType = getNamedType(info.returnType); const fields = isObjectType(returnType) || isInterfaceType(returnType) ? returnType.getFields() : {}; const selections = info.fieldNodes; const totalCountOnly = selections.every((selection) => selection.selectionSet?.selections.every( (s) => s.kind === Kind.FIELD && (fields[s.name.value]?.extensions?.pothosPrismaTotalCount || s.name.value === '__typename'), ), ); return resolvePrismaCursorConnection( { parent, query, ctx: context, parseCursor, maxSize, defaultSize, args, totalCount: totalCount && (() => totalCount(parent, args as never, context, info)), }, formatCursor, (q) => { if (totalCountOnly) { return []; } return checkIfQueryIsUsed( this.builder, query, info, resolve(q as never, parent, args as never, context, info) as never, ); }, ); }, }, connectionOptions instanceof ObjectRef ? connectionOptions : { ...connectionOptions, fields: totalCount ? ( t: PothosSchemaTypes.ObjectFieldBuilder< SchemaTypes, { totalCount?: () => MaybePromise } >, ) => ({ totalCount: t.int({ nullable: false, extensions: { pothosPrismaTotalCount: true, }, resolve: (parent) => parent.totalCount?.(), }), ...(connectionOptions as { fields?: (t: unknown) => {} }).fields?.(t), }) : (connectionOptions as { fields: undefined }).fields, extensions: { ...(connectionOptions as Record | undefined)?.extensions, }, }, edgeOptions, ); return fieldRef; } as never; function checkIfQueryIsUsed( builder: PothosSchemaTypes.SchemaBuilder, query: object, info: GraphQLResolveInfo, result: T, ): T { const { onUnusedQuery } = builder.options.prisma || {}; if (!onUnusedQuery) { return result; } if (isThenable(result)) { return result.then((resolved) => { if (!isUsed(query)) { onUnused(); } return resolved; }) as T; } if (!isUsed(query)) { onUnused(); } return result; function onUnused() { if (typeof onUnusedQuery === 'function') { onUnusedQuery(info); return; } const message = `Prisma query was unused in resolver for ${info.parentType.name}.${info.fieldName}`; if (onUnusedQuery === 'error') { throw new PothosError(message); } if (onUnusedQuery === 'warn') { console.warn(message); } } }