11import { Field , FieldOptions } from '@nestjs/graphql' ;
2+ import { TypeMetadataStorage } from '@nestjs/graphql/dist/schema-builder/storages/type-metadata.storage' ;
23import { Prop , PropOptions } from '@nestjs/mongoose' ;
34import { ApiProperty , ApiPropertyOptions } from '@nestjs/swagger' ;
45import { EnumAllowedTypes } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface' ;
@@ -18,7 +19,7 @@ import {
1819 ValidateNested ,
1920 ValidationOptions ,
2021} from 'class-validator' ;
21- import { GraphQLScalarType } from 'graphql' ;
22+ import { GraphQLScalarType , isEnumType } from 'graphql' ;
2223
2324import { RoleEnum } from '../enums/role.enum' ;
2425import { Restricted , RestrictedType } from './restricted.decorator' ;
@@ -27,6 +28,12 @@ import { Restricted, RestrictedType } from './restricted.decorator';
2728// Key: `${className}.${propertyName}`, Value: nested type constructor
2829export const nestedTypeRegistry = new Map < string , any > ( ) ;
2930
31+ /**
32+ * Registry to map enum objects to their names.
33+ * This is populated when registerEnumType is called or can be manually populated.
34+ */
35+ export const enumNameRegistry = new Map < any , string > ( ) ;
36+
3037export interface UnifiedFieldOptions {
3138 /** Description used for both Swagger & Gql */
3239 description ?: string ;
@@ -166,7 +173,18 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
166173 if ( opts . enum && opts . enum . enum ) {
167174 swaggerOpts . enum = opts . enum . enum ;
168175
169- if ( opts . enum . enumName ) {
176+ // Set enumName with auto-detection:
177+ // - If enumName property doesn't exist at all, auto-detect the name
178+ // - If enumName is explicitly set (even to null/undefined), use that value
179+ // This allows explicit opts.enum.enumName = undefined to disable auto-detection
180+ if ( ! ( 'enumName' in opts . enum ) ) {
181+ // Property doesn't exist, try auto-detection
182+ const autoDetectedName = getEnumName ( opts . enum . enum ) ;
183+ if ( autoDetectedName ) {
184+ swaggerOpts . enumName = autoDetectedName ;
185+ }
186+ } else {
187+ // Property exists (even if undefined/null), use its value
170188 swaggerOpts . enumName = opts . enum . enumName ;
171189 }
172190
@@ -276,11 +294,60 @@ function getBuiltInValidator(
276294 return null ;
277295 }
278296 if ( each ) {
279- return ( t , k ) => decorator ( target , k ) ;
297+ return ( _t , k ) => decorator ( target , k ) ;
280298 }
281299 return decorator ;
282300}
283301
302+ /**
303+ * Helper function to extract enum name from an enum object
304+ * Attempts multiple strategies to find a meaningful name
305+ */
306+ function getEnumName ( enumObj : any ) : string | undefined {
307+ // Check if the enum was registered in our custom registry
308+ if ( enumNameRegistry . has ( enumObj ) ) {
309+ return enumNameRegistry . get ( enumObj ) ;
310+ }
311+
312+ // Check if it's registered in GraphQL TypeMetadataStorage (most common case with registerEnumType)
313+ try {
314+ const enumsMetadata = TypeMetadataStorage . getEnumsMetadata ( ) ;
315+ const matchingEnum = enumsMetadata . find ( ( metadata ) => metadata . ref === enumObj ) ;
316+ if ( matchingEnum && matchingEnum . name ) {
317+ return matchingEnum . name ;
318+ }
319+ } catch ( error ) {
320+ // TypeMetadataStorage might not be initialized yet during bootstrap
321+ // This is not an error, we just continue with other strategies
322+ }
323+
324+ // Check if it's a GraphQL enum type
325+ if ( isEnumType ( enumObj ) ) {
326+ return enumObj . name ;
327+ }
328+
329+ // Check if the enum object has a name property (some custom implementations)
330+ if ( enumObj && typeof enumObj === 'object' && 'name' in enumObj && typeof enumObj . name === 'string' ) {
331+ return enumObj . name ;
332+ }
333+
334+ // Check if it's a constructor function with a name
335+ if ( typeof enumObj === 'function' && enumObj . name && enumObj . name !== 'Object' ) {
336+ return enumObj . name ;
337+ }
338+
339+ // For regular TypeScript enums, try to find the global variable name
340+ // This is a heuristic approach - not guaranteed to work in all cases
341+ if ( enumObj && typeof enumObj === 'object' ) {
342+ // Check constructor name (though this usually returns 'Object' for enums)
343+ if ( enumObj . constructor && enumObj . constructor . name && enumObj . constructor . name !== 'Object' ) {
344+ return enumObj . constructor . name ;
345+ }
346+ }
347+
348+ return undefined ;
349+ }
350+
284351function isGraphQLScalar ( type : any ) : boolean {
285352 // CustomScalar check (The CustomScalar interface implements these functions below)
286353 return (
0 commit comments