import { JSONObjectResolver } from 'graphql-scalars' import PrismaPlugin, { PothosPrismaDatamodel, PrismaClient } from '@pothos/plugin-prisma' import { Request, Response } from 'express' import SmartSubscriptionPlugin, { subscribeOptionsFromIterator } from '@pothos/plugin-smart-subscriptions' import ZodPlugin from '@pothos/plugin-zod' import AuthzPlugin from '@pothos/plugin-authz' import ErrorsPlugin from '@pothos/plugin-errors' // @ts-expect-error import type { FileUpload } from 'graphql-upload/processRequest.mjs' // @ts-expect-error import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs' import { Injectable, Logger } from '@nestjs/common' import { PrismaCrudGenerator } from './graphql.generator' import type PrismaTypes from '../types/pothos.generated' import PrismaUtils from '@pothos/plugin-prisma-utils' import { PubSub } from 'graphql-subscriptions' import RelayPlugin from '@pothos/plugin-relay' import SchemaBuilder from '@pothos/core' import SimpleObjectPlugin from '@pothos/plugin-simple-objects' import { User } from '@prisma/client' import { getDatamodel } from '../types/pothos.generated' import { DateTime } from 'luxon' import { Kind, ValueNode } from 'graphql' import { DateTimeUtils } from '../common/utils/datetime.utils' import { JsonValue } from '@prisma/client/runtime/library' import Delta from 'quill-delta' export type SchemaContext = | { isSubscription: true websocket: { req: Request pubSub: PubSub sessionId: string me: User generator: PrismaCrudGenerator } } | { isSubscription: false http: { req: Request res: Response me: User | null pubSub: PubSub invalidateCache: () => Promise generator: PrismaCrudGenerator } } // extend prisma types to contain string type export interface SchemaBuilderOption { Context: SchemaContext PrismaTypes: PrismaTypes DataModel: PothosPrismaDatamodel Connection: { totalCount: number | (() => number | Promise) } // AuthZRule: keyof typeof rules; Scalars: { DateTime: { Input: string | DateTime | Date Output: string | DateTime | Date } Json: { Input: JsonValue Output: JsonValue } Upload: { Input: FileUpload Output: FileUpload } Int: { Input: number Output: number | bigint | string } Delta: { Input: Delta Output: Delta } } } @Injectable() export class Builder extends SchemaBuilder { public generator: PrismaCrudGenerator constructor(private readonly prisma: PrismaClient) { super({ plugins: [ PrismaPlugin, PrismaUtils, SimpleObjectPlugin, SmartSubscriptionPlugin, RelayPlugin, ErrorsPlugin, AuthzPlugin, ZodPlugin, ], smartSubscriptions: { debounceDelay: 1000, ...subscribeOptionsFromIterator((name, context) => { return context.isSubscription ? context.websocket.pubSub.asyncIterableIterator(name) : context.http.pubSub.asyncIterableIterator(name) }), }, zod: { // optionally customize how errors are formatted validationError: (zodError, _args, _context, _info) => { // the default behavior is to just throw the zod error directly Logger.error(zodError.message, 'Zod Error') return zodError }, }, relay: {}, prisma: { client: prisma, exposeDescriptions: true, filterConnectionTotalCount: true, onUnusedQuery: (info) => { Logger.log(`Unused query: ${info.fieldName}`, 'GraphQL') }, dmmf: getDatamodel(), }, errors: { defaultTypes: [], }, }) this.generator = new PrismaCrudGenerator(this) this.scalarType('DateTime', { serialize: (value) => { // Serialize outgoing DateTime to ISO string if (typeof value === 'string') { return value } if (typeof value === 'object' && value !== null && 'toISO' in value) { return value } // if value = Date, convert to DateTime if (value instanceof Date) { return DateTimeUtils.toIsoString(DateTimeUtils.fromDate(value)) } throw new Error('Invalid DateTime') }, parseValue: (value) => { // Parse incoming ISO string to Luxon DateTime if (typeof value === 'string') { return DateTimeUtils.fromIsoString(value) } throw new Error('Invalid DateTime') }, parseLiteral: (ast) => { // parse string to DateTime if (ast.kind === Kind.STRING) { return DateTimeUtils.fromIsoString(ast.value) } throw new Error('Invalid DateTime') }, }) this.scalarType('Delta', { serialize: (value) => JSON.stringify(value), parseValue: (value: unknown) => JSON.parse(value as string) as Delta, parseLiteral: (ast: ValueNode) => ast as unknown as Delta, }) this.addScalarType('Json', JSONObjectResolver) this.addScalarType('Upload', GraphQLUpload) this.queryType({}) this.mutationType({}) this.subscriptionType({}) this.globalConnectionField('totalCount', (t) => t.int({ nullable: true, resolve: (parent) => (typeof parent.totalCount === 'function' ? parent.totalCount() : parent.totalCount), }), ) } } export type BuilderTypes = PothosSchemaTypes.ExtendDefaultTypes