import type { SchemaComposer , Resolver , InputTypeComposer } from 'graphql-compose' ; import { schemaComposer as globalSchemaComposer , ObjectTypeComposer } from 'graphql-compose' ; import type { Model , Document } from 'mongoose' ; import { convertModelToGraphQL } from './fieldsConverter' ; import { resolverFactory } from './resolvers' ; import MongoID from './types/MongoID' ; export type TypeConverterInputTypeOpts = { /**
     * What should be input type name.
     * By default: baseTypeName + 'Input'
     */ name ? : string ; /**
     * Provide arbitrary description for generated type.
     */ description ? : string ; /**
     * You can leave only whitelisted fields in type via this option.
     * Any other fields will be removed.
     */ onlyFields ? : string [ ] ; /**
     * You an remove some fields from type via this option.
     */ removeFields ? : string [ ] ; /**
     * This option makes provided fieldNames as required
     */ requiredFields ? : string [ ] ; deprecated fields ? : { deprecated only ? : string [ ] ; deprecated remove ? : string [ ] ; deprecated required ? : string [ ] ; } ; } ; export type ComposeMongooseOpts < TContext = any > = { /**
     * Which type registry use for generated types.
     * By default is used global default registry.
     */ schemaComposer ? : SchemaComposer < TContext > ; /**
     * What should be base type name for generated type from mongoose model.
     */ name ? : string ; /**
     * Provide arbitrary description for generated type.
     */ description ? : string ; /**
     * You can leave only whitelisted fields in type via this option.
     * Any other fields will be removed.
     */ onlyFields ? : string [ ] ; /**
     * You an remove some fields from type via this option.
     */ removeFields ? : string [ ] ; /**
     * You may configure generated InputType
     */ inputType ? : TypeConverterInputTypeOpts ; /**
     * You can make fields as NonNull if they have default value in mongoose model.
     */ defaultsAsNonNull ? : boolean ; deprecated fields ? : { deprecated only ? : string [ ] ; deprecated remove ? : string [ ] ; } ; } ; export type GenerateResolverType < TDoc extends Document , TContext = any > = { [ resolver in keyof typeof resolverFactory ] : < TSource = any > ( opts ? : Parameters < typeof resolverFactory [ resolver ] > [ 2 ] ) => typeof resolverFactory [ resolver ] extends ( ... args : any ) => Resolver < any , any , infer TArgs , any > ? Resolver < TSource , TContext , TArgs , TDoc > : any ; } ; export type ObjectTypeComposerWithMongooseResolvers < TDoc extends Document , TContext = any > = ObjectTypeComposer < TDoc , TContext > & { mongooseResolvers : GenerateResolverType < TDoc , TContext > ; } ; export function composeMongoose < TDoc extends Document , TContext = any > ( model , opts = { } ) { const m = model as Model < any > ; const name = ( opts && opts . name ) || m . modelName ; const sc = opts . schemaComposer || globalSchemaComposer ; sc . add ( MongoID ) ; if ( sc . has ( name ) ) { throw new Error ( `You try to generate GraphQL Type with name ${ name } from mongoose model but this type already exists in SchemaComposer. Please choose another type name "composeWithMongoose(model, { name: 'NewTypeName' })", or reuse existed type "schemaComposer.getOTC('TypeName')", or remove type from SchemaComposer before calling composeWithMongoose method "schemaComposer.delete('TypeName')".` ) ; } if ( sc . has ( m . schema ) ) { sc . delete ( m . schema ) ; } const tc = convertModelToGraphQL ( m , name , sc ) ; if ( opts . description ) { tc . setDescription ( opts . description ) ; } prepareFields ( tc , opts ) ; createInputType ( tc , opts . inputType ) ; if ( opts . defaultsAsNonNull ) { makeFieldsNonNullWithDefaultValues ( tc ) ; } tc . makeFieldNonNull ( '_id' ) ; const mongooseResolvers = { } as any ; Object . keys ( resolverFactory ) . forEach ( ( name ) => { mongooseResolvers [ name ] = ( resolverFactory as any ) [ name ] . bind ( undefined , model , tc ) ; } ) ; ( tc as any ) . mongooseResolvers = mongooseResolvers ; return tc as any ; } function makeFieldsNonNullWithDefaultValues ( tc , alreadyWorked = new Set ( ) ) { if ( alreadyWorked . has ( tc ) ) return ; alreadyWorked . add ( tc ) ; let hasFieldsWithDefaultValues = false ; tc . getFieldNames ( ) . forEach ( ( fieldName ) => { const fc = tc . getField ( fieldName ) ; if ( fc . type instanceof ObjectTypeComposer ) { if ( fc . extensions ?. isSingleNestedMongooseSchema ) { return ; } makeFieldsNonNullWithDefaultValues ( fc . type ) ; if ( fc . type . getExtension ( 'hasFieldsWithDefaultValue' ) ) { tc . makeFieldNonNull ( fieldName ) ; } } const defaultValue = fc ?. extensions ?. defaultValue ; if ( defaultValue !== null && defaultValue !== undefined ) { hasFieldsWithDefaultValues = true ; tc . makeFieldNonNull ( fieldName ) ; } } ) ; if ( hasFieldsWithDefaultValues ) { tc . setExtension ( 'hasFieldsWithDefaultValue' , true ) ; } } export function prepareFields ( tc , opts = { } ) { const onlyFields = opts ?. onlyFields || opts ?. fields ?. only ; if ( onlyFields ) { tc . removeOtherFields ( onlyFields ) ; } const removeFields = opts ?. removeFields || opts ?. fields ?. remove ; if ( removeFields ) { tc . removeField ( removeFields ) ; } } export function createInputType ( tc , inputTypeOpts = { } ) { const inputTypeComposer = tc . getInputTypeComposer ( ) ; if ( inputTypeOpts . name ) { inputTypeComposer . setTypeName ( inputTypeOpts . name ) ; } if ( inputTypeOpts . description ) { inputTypeComposer . setDescription ( inputTypeOpts . description ) ; } prepareInputFields ( inputTypeComposer , inputTypeOpts ) ; } export function prepareInputFields ( inputTypeComposer , inputTypeOpts = { } ) { const onlyFields = inputTypeOpts ?. onlyFields || inputTypeOpts ?. fields ?. only ; if ( onlyFields ) { inputTypeComposer . removeOtherFields ( onlyFields ) ; } const removeFields = inputTypeOpts ?. removeFields || inputTypeOpts ?. fields ?. remove ; if ( removeFields ) { inputTypeComposer . removeField ( removeFields ) ; } const requiredFields = inputTypeOpts ?. requiredFields || inputTypeOpts ?. fields ?. required ; if ( requiredFields ) { inputTypeComposer . makeFieldNonNull ( requiredFields ) ; } }