Skip to content

Commit 4c21d82

Browse files
authored
Merge pull request #446 from lenneTech/develop
Release 11.4.5
2 parents afece10 + 5423eac commit 4c21d82

File tree

8 files changed

+1060
-7
lines changed

8 files changed

+1060
-7
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lenne.tech/nest-server",
3-
"version": "11.4.5",
3+
"version": "11.4.6",
44
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
55
"keywords": [
66
"node",

spectaql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ servers:
1111
info:
1212
title: lT Nest Server
1313
description: Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).
14-
version: 11.4.5
14+
version: 11.4.6
1515
contact:
1616
name: lenne.Tech GmbH
1717
url: https://lenne.tech

src/core/common/decorators/unified-field.decorator.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Field, FieldOptions } from '@nestjs/graphql';
2+
import { TypeMetadataStorage } from '@nestjs/graphql/dist/schema-builder/storages/type-metadata.storage';
23
import { Prop, PropOptions } from '@nestjs/mongoose';
34
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
45
import { 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

2324
import { RoleEnum } from '../enums/role.enum';
2425
import { Restricted, RestrictedType } from './restricted.decorator';
@@ -27,6 +28,12 @@ import { Restricted, RestrictedType } from './restricted.decorator';
2728
// Key: `${className}.${propertyName}`, Value: nested type constructor
2829
export 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+
3037
export 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+
284351
function isGraphQLScalar(type: any): boolean {
285352
// CustomScalar check (The CustomScalar interface implements these functions below)
286353
return (
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { registerEnumType } from '@nestjs/graphql';
2+
3+
import { enumNameRegistry } from '../decorators/unified-field.decorator';
4+
5+
/**
6+
* Interface defining options for the registerEnum helper
7+
*/
8+
export interface RegisterEnumOptions<T extends object = any> {
9+
/**
10+
* Description of the enum
11+
*/
12+
description?: string;
13+
14+
/**
15+
* Whether to register the enum for GraphQL using registerEnumType
16+
* @default true
17+
*/
18+
graphql?: boolean;
19+
20+
/**
21+
* Name of the enum (required)
22+
*/
23+
name: string;
24+
25+
/**
26+
* Whether to register the enum in the enumNameRegistry for Swagger/REST
27+
* @default true
28+
*/
29+
swagger?: boolean;
30+
31+
/**
32+
* A map of options for the values of the enum (only used for GraphQL)
33+
*/
34+
valuesMap?: Partial<Record<keyof T, { deprecationReason?: string; description?: string }>>;
35+
}
36+
37+
/**
38+
* Registers an enum for both GraphQL and Swagger/REST APIs.
39+
*
40+
* This is a convenience helper that combines:
41+
* - `registerEnumType` from @nestjs/graphql (for GraphQL schema)
42+
* - Manual registration in `enumNameRegistry` (for Swagger/OpenAPI)
43+
*
44+
* @example
45+
* ```typescript
46+
* export enum StatusEnum {
47+
* ACTIVE = 'active',
48+
* INACTIVE = 'inactive'
49+
* }
50+
*
51+
* // Register for both GraphQL and REST
52+
* registerEnum(StatusEnum, {
53+
* name: 'StatusEnum',
54+
* description: 'User status'
55+
* });
56+
*
57+
* // Register only for REST (no GraphQL)
58+
* registerEnum(StatusEnum, {
59+
* name: 'StatusEnum',
60+
* graphql: false
61+
* });
62+
*
63+
* // Register only for GraphQL (no REST)
64+
* registerEnum(StatusEnum, {
65+
* name: 'StatusEnum',
66+
* swagger: false
67+
* });
68+
* ```
69+
*
70+
* @param enumRef - The enum reference to register
71+
* @param options - Registration options
72+
*/
73+
export function registerEnum<T extends object = any>(enumRef: T, options: RegisterEnumOptions<T>): void {
74+
const { description, graphql = true, name, swagger = true, valuesMap } = options;
75+
76+
if (!name) {
77+
throw new Error('Enum name is required for registerEnum');
78+
}
79+
80+
// Register for Swagger/REST if enabled
81+
if (swagger) {
82+
enumNameRegistry.set(enumRef, name);
83+
}
84+
85+
// Register for GraphQL if enabled
86+
if (graphql) {
87+
registerEnumType(enumRef, {
88+
description,
89+
name,
90+
valuesMap,
91+
});
92+
}
93+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export * from './core/common/helpers/graphql.helper';
3737
export * from './core/common/helpers/gridfs.helper';
3838
export * from './core/common/helpers/input.helper';
3939
export * from './core/common/helpers/model.helper';
40+
export * from './core/common/helpers/register-enum.helper';
4041
export * from './core/common/helpers/scim.helper';
4142
export * from './core/common/helpers/service.helper';
4243
export * from './core/common/helpers/table.helper';

0 commit comments

Comments
 (0)