Skip to content

Commit f3648b0

Browse files
fix: several minor fixes, fix mapeed types & swc integration
1 parent 9500820 commit f3648b0

13 files changed

Lines changed: 249 additions & 153 deletions

e2e/api-spec.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,15 @@
10421042
}
10431043
}
10441044
},
1045+
"enumWithDescription": {
1046+
"enum": [
1047+
"A",
1048+
"B",
1049+
"C"
1050+
],
1051+
"type": "string",
1052+
"description": "Enum with description"
1053+
},
10451054
"enum": {
10461055
"$ref": "#/components/schemas/LettersEnum"
10471056
},
@@ -1068,6 +1077,7 @@
10681077
"createdAt",
10691078
"urls",
10701079
"options",
1080+
"enumWithDescription",
10711081
"enum",
10721082
"enumArr"
10731083
]

e2e/src/cats/dto/create-cat.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export class CreateCatDto {
4242
})
4343
readonly options?: Record<string, any>[];
4444

45+
@ApiProperty({
46+
description: 'Enum with description'
47+
})
48+
readonly enumWithDescription: LettersEnum;
49+
4550
@ApiProperty({
4651
enum: LettersEnum,
4752
enumName: 'LettersEnum'

e2e/validate-schema.e2e-spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ describe('Validate OpenAPI schema', () => {
6767
import('./src/cats/dto/create-cat.dto'),
6868
{
6969
CreateCatDto: {
70+
enumWithDescription: {
71+
enum: await import(
72+
'./src/cats/dto/pagination-query.dto'
73+
).then((f) => f.LettersEnum)
74+
},
7075
name: {
7176
description: 'Name of the cat'
7277
}

lib/plugin/metadata-loader.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { METADATA_FACTORY_NAME } from './plugin-constants';
22

33
export class MetadataLoader {
4+
private static readonly refreshHooks = new Array<() => void>();
5+
6+
static addRefreshHook(hook: () => void) {
7+
return MetadataLoader.refreshHooks.unshift(hook);
8+
}
9+
410
async load(metadata: Record<string, any>) {
511
const pkgMetadata = metadata['@nestjs/swagger'];
612
if (!pkgMetadata) {
@@ -13,9 +19,12 @@ export class MetadataLoader {
1319
if (controllers) {
1420
await this.applyMetadata(controllers);
1521
}
22+
this.runHooks();
1623
}
1724

18-
private async applyMetadata(meta: Record<string, any>) {
25+
private async applyMetadata(
26+
meta: Array<[Promise<unknown>, Record<string, any>]>
27+
) {
1928
const loadPromises = meta.map(async ([fileImport, fileMeta]) => {
2029
const fileRef = await fileImport;
2130
Object.keys(fileMeta).map((key) => {
@@ -25,4 +34,8 @@ export class MetadataLoader {
2534
});
2635
await Promise.all(loadPromises);
2736
}
37+
38+
private runHooks() {
39+
MetadataLoader.refreshHooks.forEach((hook) => hook());
40+
}
2841
}

lib/type-helpers/intersection-type.helper.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Type } from '@nestjs/common';
22
import {
3-
inheritTransformationMetadata,
4-
inheritValidationMetadata,
53
inheritPropertyInitializers,
4+
inheritTransformationMetadata,
5+
inheritValidationMetadata
66
} from '@nestjs/mapped-types';
77
import { DECORATORS } from '../constants';
88
import { ApiProperty } from '../decorators';
9+
import { MetadataLoader } from '../plugin/metadata-loader';
910
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
1011
import { clonePluginMetadataFactory } from './mapped-types.utils';
1112

@@ -42,19 +43,29 @@ export function IntersectionType<T extends Type[]>(...classRefs: T) {
4243
inheritValidationMetadata(classRef, IntersectionClassType);
4344
inheritTransformationMetadata(classRef, IntersectionClassType);
4445

45-
clonePluginMetadataFactory(
46-
IntersectionClassType as Type<unknown>,
47-
classRef.prototype
48-
);
46+
function applyFields(fields: string[]) {
47+
clonePluginMetadataFactory(
48+
IntersectionClassType as Type<unknown>,
49+
classRef.prototype
50+
);
51+
52+
fields.forEach((propertyKey) => {
53+
const metadata = Reflect.getMetadata(
54+
DECORATORS.API_MODEL_PROPERTIES,
55+
classRef.prototype,
56+
propertyKey
57+
);
58+
const decoratorFactory = ApiProperty(metadata);
59+
decoratorFactory(IntersectionClassType.prototype, propertyKey);
60+
});
61+
}
62+
applyFields(fields);
4963

50-
fields.forEach((propertyKey) => {
51-
const metadata = Reflect.getMetadata(
52-
DECORATORS.API_MODEL_PROPERTIES,
53-
classRef.prototype,
54-
propertyKey
64+
MetadataLoader.addRefreshHook(() => {
65+
const fields = modelPropertiesAccessor.getModelProperties(
66+
classRef.prototype
5567
);
56-
const decoratorFactory = ApiProperty(metadata);
57-
decoratorFactory(IntersectionClassType.prototype, propertyKey);
68+
applyFields(fields);
5869
});
5970
});
6071

lib/type-helpers/omit-type.helper.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { omit } from 'lodash';
88
import { DECORATORS } from '../constants';
99
import { ApiProperty } from '../decorators';
10+
import { MetadataLoader } from '../plugin/metadata-loader';
1011
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
1112
import { clonePluginMetadataFactory } from './mapped-types.utils';
1213

@@ -15,7 +16,7 @@ const modelPropertiesAccessor = new ModelPropertiesAccessor();
1516
export function OmitType<T, K extends keyof T>(
1617
classRef: Type<T>,
1718
keys: readonly K[]
18-
): Type<Omit<T, typeof keys[number]>> {
19+
): Type<Omit<T, (typeof keys)[number]>> {
1920
const fields = modelPropertiesAccessor
2021
.getModelProperties(classRef.prototype)
2122
.filter((item) => !keys.includes(item as K));
@@ -31,20 +32,32 @@ export function OmitType<T, K extends keyof T>(
3132
inheritValidationMetadata(classRef, OmitTypeClass, isInheritedPredicate);
3233
inheritTransformationMetadata(classRef, OmitTypeClass, isInheritedPredicate);
3334

34-
clonePluginMetadataFactory(
35-
OmitTypeClass as Type<unknown>,
36-
classRef.prototype,
37-
(metadata: Record<string, any>) => omit(metadata, keys)
38-
);
39-
40-
fields.forEach((propertyKey) => {
41-
const metadata = Reflect.getMetadata(
42-
DECORATORS.API_MODEL_PROPERTIES,
35+
function applyFields(fields: string[]) {
36+
clonePluginMetadataFactory(
37+
OmitTypeClass as Type<unknown>,
4338
classRef.prototype,
44-
propertyKey
39+
(metadata: Record<string, any>) => omit(metadata, keys)
4540
);
46-
const decoratorFactory = ApiProperty(metadata);
47-
decoratorFactory(OmitTypeClass.prototype, propertyKey);
41+
42+
fields.forEach((propertyKey) => {
43+
const metadata = Reflect.getMetadata(
44+
DECORATORS.API_MODEL_PROPERTIES,
45+
classRef.prototype,
46+
propertyKey
47+
);
48+
const decoratorFactory = ApiProperty(metadata);
49+
decoratorFactory(OmitTypeClass.prototype, propertyKey);
50+
});
51+
}
52+
applyFields(fields);
53+
54+
MetadataLoader.addRefreshHook(() => {
55+
const fields = modelPropertiesAccessor
56+
.getModelProperties(classRef.prototype)
57+
.filter((item) => !keys.includes(item as K));
58+
59+
applyFields(fields);
4860
});
49-
return OmitTypeClass as Type<Omit<T, typeof keys[number]>>;
61+
62+
return OmitTypeClass as Type<Omit<T, (typeof keys)[number]>>;
5063
}

lib/type-helpers/partial-type.helper.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { mapValues } from 'lodash';
99
import { DECORATORS } from '../constants';
1010
import { ApiProperty } from '../decorators';
11+
import { MetadataLoader } from '../plugin/metadata-loader';
1112
import { METADATA_FACTORY_NAME } from '../plugin/plugin-constants';
1213
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
1314
import { clonePluginMetadataFactory } from './mapped-types.utils';
@@ -25,27 +26,37 @@ export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
2526
inheritValidationMetadata(classRef, PartialTypeClass);
2627
inheritTransformationMetadata(classRef, PartialTypeClass);
2728

28-
clonePluginMetadataFactory(
29-
PartialTypeClass as Type<unknown>,
30-
classRef.prototype,
31-
(metadata: Record<string, any>) =>
32-
mapValues(metadata, (item) => ({ ...item, required: false }))
33-
);
34-
35-
fields.forEach((key) => {
36-
const metadata =
37-
Reflect.getMetadata(
38-
DECORATORS.API_MODEL_PROPERTIES,
39-
classRef.prototype,
40-
key
41-
) || {};
42-
43-
const decoratorFactory = ApiProperty({
44-
...metadata,
45-
required: false
29+
function applyFields(fields: string[]) {
30+
clonePluginMetadataFactory(
31+
PartialTypeClass as Type<unknown>,
32+
classRef.prototype,
33+
(metadata: Record<string, any>) =>
34+
mapValues(metadata, (item) => ({ ...item, required: false }))
35+
);
36+
37+
fields.forEach((key) => {
38+
const metadata =
39+
Reflect.getMetadata(
40+
DECORATORS.API_MODEL_PROPERTIES,
41+
classRef.prototype,
42+
key
43+
) || {};
44+
45+
const decoratorFactory = ApiProperty({
46+
...metadata,
47+
required: false
48+
});
49+
decoratorFactory(PartialTypeClass.prototype, key);
50+
applyIsOptionalDecorator(PartialTypeClass, key);
4651
});
47-
decoratorFactory(PartialTypeClass.prototype, key);
48-
applyIsOptionalDecorator(PartialTypeClass, key);
52+
}
53+
applyFields(fields);
54+
55+
MetadataLoader.addRefreshHook(() => {
56+
const fields = modelPropertiesAccessor.getModelProperties(
57+
classRef.prototype
58+
);
59+
applyFields(fields);
4960
});
5061

5162
if (PartialTypeClass[METADATA_FACTORY_NAME]) {

lib/type-helpers/pick-type.helper.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { pick } from 'lodash';
88
import { DECORATORS } from '../constants';
99
import { ApiProperty } from '../decorators';
10+
import { MetadataLoader } from '../plugin/metadata-loader';
1011
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
1112
import { clonePluginMetadataFactory } from './mapped-types.utils';
1213

@@ -15,7 +16,7 @@ const modelPropertiesAccessor = new ModelPropertiesAccessor();
1516
export function PickType<T, K extends keyof T>(
1617
classRef: Type<T>,
1718
keys: readonly K[]
18-
): Type<Pick<T, typeof keys[number]>> {
19+
): Type<Pick<T, (typeof keys)[number]>> {
1920
const fields = modelPropertiesAccessor
2021
.getModelProperties(classRef.prototype)
2122
.filter((item) => keys.includes(item as K));
@@ -32,21 +33,32 @@ export function PickType<T, K extends keyof T>(
3233
inheritValidationMetadata(classRef, PickTypeClass, isInheritedPredicate);
3334
inheritTransformationMetadata(classRef, PickTypeClass, isInheritedPredicate);
3435

35-
clonePluginMetadataFactory(
36-
PickTypeClass as Type<unknown>,
37-
classRef.prototype,
38-
(metadata: Record<string, any>) => pick(metadata, keys)
39-
);
40-
41-
fields.forEach((propertyKey) => {
42-
const metadata = Reflect.getMetadata(
43-
DECORATORS.API_MODEL_PROPERTIES,
36+
function applyFields(fields: string[]) {
37+
clonePluginMetadataFactory(
38+
PickTypeClass as Type<unknown>,
4439
classRef.prototype,
45-
propertyKey
40+
(metadata: Record<string, any>) => pick(metadata, keys)
4641
);
47-
const decoratorFactory = ApiProperty(metadata);
48-
decoratorFactory(PickTypeClass.prototype, propertyKey);
42+
43+
fields.forEach((propertyKey) => {
44+
const metadata = Reflect.getMetadata(
45+
DECORATORS.API_MODEL_PROPERTIES,
46+
classRef.prototype,
47+
propertyKey
48+
);
49+
const decoratorFactory = ApiProperty(metadata);
50+
decoratorFactory(PickTypeClass.prototype, propertyKey);
51+
});
52+
}
53+
applyFields(fields);
54+
55+
MetadataLoader.addRefreshHook(() => {
56+
const fields = modelPropertiesAccessor
57+
.getModelProperties(classRef.prototype)
58+
.filter((item) => keys.includes(item as K));
59+
60+
applyFields(fields);
4961
});
5062

51-
return PickTypeClass as Type<Pick<T, typeof keys[number]>>;
63+
return PickTypeClass as Type<Pick<T, (typeof keys)[number]>>;
5264
}

0 commit comments

Comments
 (0)