diff --git a/lib/factories/definitions.factory.ts b/lib/factories/definitions.factory.ts index 5787e330..aa84b77d 100644 --- a/lib/factories/definitions.factory.ts +++ b/lib/factories/definitions.factory.ts @@ -29,6 +29,8 @@ export class DefinitionsFactory { } schemaMetadata.properties?.forEach((item) => { const options = this.inspectTypeDefinition(item.options as any); + this.inspectRef(item.options as any); + schemaDefinition = { [item.propertyKey]: options as any, ...schemaDefinition, @@ -49,6 +51,10 @@ export class DefinitionsFactory { } else if (this.isMongooseSchemaType(optionsOrType)) { return optionsOrType; } + const isClass = /^class\s/.test( + Function.prototype.toString.call(optionsOrType), + ); + optionsOrType = isClass ? optionsOrType : optionsOrType(); const schemaDefinition = this.createForClass( optionsOrType as Type, @@ -77,6 +83,22 @@ export class DefinitionsFactory { return optionsOrType; } + private static inspectRef( + optionsOrType: mongoose.SchemaTypeOptions | Function, + ) { + if (!optionsOrType || typeof optionsOrType !== 'object') { + return; + } + if (typeof optionsOrType?.ref === 'function') { + optionsOrType.ref = + (optionsOrType.ref as Function)()?.name ?? optionsOrType.ref; + } else if (Array.isArray(optionsOrType.type)) { + if (optionsOrType.type.length > 0) { + this.inspectRef(optionsOrType.type[0]); + } + } + } + private static isPrimitive(type: Function) { return BUILT_IN_TYPES.includes(type); } diff --git a/tests/e2e/schema-definitions.factory.spec.ts b/tests/e2e/schema-definitions.factory.spec.ts index 2ab6485c..57b10cbe 100644 --- a/tests/e2e/schema-definitions.factory.spec.ts +++ b/tests/e2e/schema-definitions.factory.spec.ts @@ -2,6 +2,15 @@ import * as mongoose from 'mongoose'; import { DefinitionsFactory, Prop, raw, Schema } from '../../lib'; import { CannotDetermineTypeError } from '../../lib/errors'; +@Schema() +class RefClass { + @Prop() + title: string; + + @Prop({ type: mongoose.Schema.Types.ObjectId, ref: () => ExampleClass }) + host; +} + @Schema() class ChildClass { @Prop() @@ -45,8 +54,16 @@ class ExampleClass { @Prop() number: number; + @Prop({ + type: [{ type: mongoose.Schema.Types.ObjectId, ref: () => RefClass }], + }) + ref: RefClass[]; + @Prop({ required: true }) - children: ChildClass; + child: ChildClass; + + @Prop({ type: () => ChildClass }) + child2: ChildClass; @Prop([ChildClass]) nodes: ChildClass[]; @@ -71,6 +88,14 @@ describe('DefinitionsFactory', () => { objectId: { type: mongoose.Schema.Types.ObjectId, }, + ref: { + type: [ + { + ref: 'RefClass', + type: mongoose.Schema.Types.ObjectId, + }, + ], + }, name: { required: true, type: String, @@ -87,7 +112,7 @@ describe('DefinitionsFactory', () => { ], buffer: { type: mongoose.Schema.Types.Buffer }, decimal: { type: mongoose.Schema.Types.Decimal128 }, - children: { + child: { required: true, type: { id: { @@ -98,6 +123,16 @@ describe('DefinitionsFactory', () => { }, }, }, + child2: { + type: { + id: { + type: Number, + }, + name: { + type: String, + }, + }, + }, any: { type: mongoose.Schema.Types.Mixed }, array: { type: [] }, customArray: [{ custom: 'literal', object: true }], @@ -117,6 +152,19 @@ describe('DefinitionsFactory', () => { }); }); + it('should generate a valid schema definition (class reference) for cyclic deps', () => { + const refClassDefinition = DefinitionsFactory.createForClass(RefClass); + expect(refClassDefinition).toEqual({ + host: { + ref: 'ExampleClass', + type: mongoose.Schema.Types.ObjectId, + }, + title: { + type: String, + }, + }); + }); + it('should throw an error when type is ambiguous', () => { try { class AmbiguousField {