import { FactoryProvider } from '@nestjs/common/interfaces' import { getDiscriminatorModelForClass, getModelForClass } from '@typegoose/typegoose' import { isClass } from 'is-class' import { Connection } from 'mongoose' import { TypegooseClass, TypegooseClassWithOptions, TypegooseDiscriminator } from './typegoose-class.interface' import { getConnectionToken, getModelToken } from './typegoose.utils' type ModelFactory = (c: Connection) => any /** * Creates the nestjs providers for the provided models. * @param connectionName the name of the mongoose connection * @param models the models to create the nestjs providers * @returns the model providers. * @internal */ export function createTypegooseProviders( connectionName: string, models: TypegooseClassWithOptions[] = [] ): FactoryProvider[] { const connectionToken = getConnectionToken(connectionName) const buildProvider = ({ name }: TypegooseClass, modelFactory: ModelFactory) => ({ provide: getModelToken(name), useFactory: modelFactory, inject: [connectionToken] }) const createDiscriminatorFactoryFrom = (parentFactory: ModelFactory) => (discriminatorDefinition: TypegooseClass | TypegooseDiscriminator) => { if (isTypegooseClass(discriminatorDefinition)) { return buildProvider(discriminatorDefinition, (connection: Connection) => getDiscriminatorModelForClass(parentFactory(connection), discriminatorDefinition) ) } const { typegooseClass, discriminatorId } = discriminatorDefinition return buildProvider(typegooseClass, (connection: Connection) => getDiscriminatorModelForClass(parentFactory(connection), typegooseClass, discriminatorId) ) } return models.reduce((providers, { typegooseClass, schemaOptions = {}, discriminators = [] }) => { const modelFactory = (connection: Connection) => getModelForClass(typegooseClass, { existingConnection: connection, schemaOptions }) const modelProvider = buildProvider(typegooseClass, modelFactory) const discriminatorProviders = discriminators.map(createDiscriminatorFactoryFrom(modelFactory)) return [...providers, modelProvider, ...discriminatorProviders] }, []) } type ClassOrDiscriminator = TypegooseClassWithOptions | TypegooseDiscriminator type TypegooseInput = TypegooseClass | ClassOrDiscriminator /** * Santizes the input to a common object form used for creating providers. * @param item varied general input to convert * @returns a santized generic form * @internal */ export function convertToTypegooseClassWithOptions(item: TypegooseInput): TypegooseClassWithOptions { const tcwo: TypegooseClassWithOptions = convertToOptions(item) if (tcwo) { if (tcwo.discriminators) { tcwo.discriminators = tcwo.discriminators.map( (d) => convertToOptions(d) || invalidObject('discriminator') ) } return tcwo } return invalidObject('model') } /** * Returns whether or not something is a class. * @param item the value to check whether or not it is a class * @internal */ const isTypegooseClass = (item): item is TypegooseClass => isClass(item) /** * Returns whether or not a value is a typegoose class with options. * @param item the value to check whether or not it is a typegoose class with options * @internal */ const isTypegooseClassWithOptions = (item): item is TypegooseClassWithOptions => isTypegooseClass(item.typegooseClass) /** * Converts a model class to valid typegoose class structure. * @param item the item to convert * @returns the valid typegoose class * @internal */ function convertToOptions(item: TypegooseInput): ClassOrDiscriminator | undefined { if (isTypegooseClass(item)) { return { typegooseClass: item } } else if (isTypegooseClassWithOptions(item)) { return item } } /** * Throws error representing an invalid provided value. * @param type the invalid type * @internal */ function invalidObject(type: string): never { throw new Error(`Invalid ${type} object`) }