- Updated pub/sub iterator methods from `asyncIterableIterator` to `asyncIterator` in multiple schema files for improved compatibility with the latest GraphQL subscriptions. - Refactored subscription logic in CollaborationSession, Document, Message, User, and other schema files to enhance readability and maintainability. - Adjusted imports in GraphQL builder to utilize RedisPubSub for better performance in subscription handling.
182 lines
5.5 KiB
TypeScript
182 lines
5.5 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common'
|
|
import SchemaBuilder from '@pothos/core'
|
|
import AuthzPlugin from '@pothos/plugin-authz'
|
|
import ErrorsPlugin from '@pothos/plugin-errors'
|
|
import PrismaPlugin, { PothosPrismaDatamodel, PrismaClient } from '@pothos/plugin-prisma'
|
|
import PrismaUtils from '@pothos/plugin-prisma-utils'
|
|
import RelayPlugin from '@pothos/plugin-relay'
|
|
import SimpleObjectPlugin from '@pothos/plugin-simple-objects'
|
|
import SmartSubscriptionPlugin, { subscribeOptionsFromIterator } from '@pothos/plugin-smart-subscriptions'
|
|
import ZodPlugin from '@pothos/plugin-zod'
|
|
import { User } from '@prisma/client'
|
|
import { JsonValue } from '@prisma/client/runtime/library'
|
|
import { Request, Response } from 'express'
|
|
import { Kind, ValueNode } from 'graphql'
|
|
import { RedisPubSub } from 'graphql-redis-subscriptions'
|
|
import { JSONObjectResolver } from 'graphql-scalars'
|
|
// @ts-expect-error
|
|
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'
|
|
// @ts-expect-error
|
|
import type { FileUpload } from 'graphql-upload/processRequest.mjs'
|
|
import { DateTime } from 'luxon'
|
|
import Delta from 'quill-delta'
|
|
import { DateTimeUtils } from '../common/utils/datetime.utils'
|
|
import type PrismaTypes from '../types/pothos.generated'
|
|
import { getDatamodel } from '../types/pothos.generated'
|
|
import { PrismaCrudGenerator } from './graphql.generator'
|
|
|
|
export type SchemaContext =
|
|
| {
|
|
isSubscription: true
|
|
websocket: {
|
|
req: Request
|
|
pubSub: RedisPubSub
|
|
sessionId: string
|
|
me: User
|
|
generator: PrismaCrudGenerator<BuilderTypes>
|
|
}
|
|
}
|
|
| {
|
|
isSubscription: false
|
|
http: {
|
|
req: Request
|
|
res: Response
|
|
me: User | null
|
|
pubSub: RedisPubSub
|
|
invalidateCache: () => Promise<void>
|
|
generator: PrismaCrudGenerator<BuilderTypes>
|
|
}
|
|
}
|
|
|
|
// extend prisma types to contain string type
|
|
export interface SchemaBuilderOption {
|
|
Context: SchemaContext
|
|
PrismaTypes: PrismaTypes
|
|
DataModel: PothosPrismaDatamodel
|
|
Connection: {
|
|
totalCount: number | (() => number | Promise<number>)
|
|
}
|
|
// 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<SchemaBuilderOption> {
|
|
public generator: PrismaCrudGenerator<BuilderTypes>
|
|
|
|
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.asyncIterator(name)
|
|
: context.http.pubSub.asyncIterator(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<BuilderTypes>(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<SchemaBuilderOption>
|