@lenne.tech/nest-server 11.4.4 → 11.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/common/decorators/unified-field.decorator.d.ts +2 -0
- package/dist/core/common/decorators/unified-field.decorator.js +49 -3
- package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
- package/dist/core/common/helpers/register-enum.helper.d.ts +11 -0
- package/dist/core/common/helpers/register-enum.helper.js +22 -0
- package/dist/core/common/helpers/register-enum.helper.js.map +1 -0
- package/dist/core/common/pipes/map-and-validate.pipe.js +489 -16
- package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/decorators/unified-field.decorator.ts +88 -4
- package/src/core/common/helpers/register-enum.helper.ts +93 -0
- package/src/core/common/pipes/map-and-validate.pipe.ts +659 -19
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.4.
|
|
3
|
+
"version": "11.4.6",
|
|
4
4
|
"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).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Field, FieldOptions } from '@nestjs/graphql';
|
|
2
|
+
import { TypeMetadataStorage } from '@nestjs/graphql/dist/schema-builder/storages/type-metadata.storage';
|
|
2
3
|
import { Prop, PropOptions } from '@nestjs/mongoose';
|
|
3
4
|
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
|
|
4
5
|
import { EnumAllowedTypes } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface';
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
IsArray,
|
|
8
9
|
IsBoolean,
|
|
9
10
|
IsDate,
|
|
11
|
+
IsDefined,
|
|
10
12
|
IsEnum,
|
|
11
13
|
IsNotEmpty,
|
|
12
14
|
IsNumber,
|
|
@@ -17,11 +19,21 @@ import {
|
|
|
17
19
|
ValidateNested,
|
|
18
20
|
ValidationOptions,
|
|
19
21
|
} from 'class-validator';
|
|
20
|
-
import { GraphQLScalarType } from 'graphql';
|
|
22
|
+
import { GraphQLScalarType, isEnumType } from 'graphql';
|
|
21
23
|
|
|
22
24
|
import { RoleEnum } from '../enums/role.enum';
|
|
23
25
|
import { Restricted, RestrictedType } from './restricted.decorator';
|
|
24
26
|
|
|
27
|
+
// Registry to store nested type information for validation
|
|
28
|
+
// Key: `${className}.${propertyName}`, Value: nested type constructor
|
|
29
|
+
export const nestedTypeRegistry = new Map<string, any>();
|
|
30
|
+
|
|
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
|
+
|
|
25
37
|
export interface UnifiedFieldOptions {
|
|
26
38
|
/** Description used for both Swagger & Gql */
|
|
27
39
|
description?: string;
|
|
@@ -128,6 +140,8 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
128
140
|
swaggerOpts.nullable = true;
|
|
129
141
|
swaggerOpts.required = false;
|
|
130
142
|
} else {
|
|
143
|
+
// Use IsDefined to ensure field is present, then IsNotEmpty to ensure it's not empty
|
|
144
|
+
IsDefined()(target, propertyKey);
|
|
131
145
|
IsNotEmpty()(target, propertyKey);
|
|
132
146
|
|
|
133
147
|
gqlOpts.nullable = false;
|
|
@@ -159,7 +173,18 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
159
173
|
if (opts.enum && opts.enum.enum) {
|
|
160
174
|
swaggerOpts.enum = opts.enum.enum;
|
|
161
175
|
|
|
162
|
-
|
|
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
|
|
163
188
|
swaggerOpts.enumName = opts.enum.enumName;
|
|
164
189
|
}
|
|
165
190
|
|
|
@@ -209,10 +234,20 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
209
234
|
}
|
|
210
235
|
|
|
211
236
|
if (!opts.isAny) {
|
|
237
|
+
// Special handling for Date: needs @Type transformation even though it's "primitive"
|
|
238
|
+
// This allows ISO date strings to be transformed to Date objects before validation
|
|
239
|
+
if (baseType === Date) {
|
|
240
|
+
Type(() => Date)(target, propertyKey);
|
|
241
|
+
}
|
|
212
242
|
// Check if it's a primitive, if not apply transform
|
|
213
|
-
if (!isPrimitive(baseType) && !opts.enum && !isGraphQLScalar(baseType)) {
|
|
243
|
+
else if (!isPrimitive(baseType) && !opts.enum && !isGraphQLScalar(baseType)) {
|
|
214
244
|
Type(() => baseType)(target, propertyKey);
|
|
215
245
|
ValidateNested({ each: isArrayField })(target, propertyKey);
|
|
246
|
+
|
|
247
|
+
// Store nested type info in registry for use in MapAndValidatePipe
|
|
248
|
+
const className = target.constructor.name;
|
|
249
|
+
const registryKey = `${className}.${String(propertyKey)}`;
|
|
250
|
+
nestedTypeRegistry.set(registryKey, baseType);
|
|
216
251
|
}
|
|
217
252
|
}
|
|
218
253
|
|
|
@@ -259,11 +294,60 @@ function getBuiltInValidator(
|
|
|
259
294
|
return null;
|
|
260
295
|
}
|
|
261
296
|
if (each) {
|
|
262
|
-
return (
|
|
297
|
+
return (_t, k) => decorator(target, k);
|
|
263
298
|
}
|
|
264
299
|
return decorator;
|
|
265
300
|
}
|
|
266
301
|
|
|
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
|
+
|
|
267
351
|
function isGraphQLScalar(type: any): boolean {
|
|
268
352
|
// CustomScalar check (The CustomScalar interface implements these functions below)
|
|
269
353
|
return (
|
|
@@ -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
|
+
}
|