Skip to content

Commit

Permalink
fix: several minor fixes, fix mapeed types & swc integration
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jul 20, 2023
1 parent 9500820 commit f3648b0
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 153 deletions.
10 changes: 10 additions & 0 deletions e2e/api-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,15 @@
}
}
},
"enumWithDescription": {
"enum": [
"A",
"B",
"C"
],
"type": "string",
"description": "Enum with description"
},
"enum": {
"$ref": "#/components/schemas/LettersEnum"
},
Expand All @@ -1068,6 +1077,7 @@
"createdAt",
"urls",
"options",
"enumWithDescription",
"enum",
"enumArr"
]
Expand Down
5 changes: 5 additions & 0 deletions e2e/src/cats/dto/create-cat.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export class CreateCatDto {
})
readonly options?: Record<string, any>[];

@ApiProperty({
description: 'Enum with description'
})
readonly enumWithDescription: LettersEnum;

@ApiProperty({
enum: LettersEnum,
enumName: 'LettersEnum'
Expand Down
5 changes: 5 additions & 0 deletions e2e/validate-schema.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ describe('Validate OpenAPI schema', () => {
import('./src/cats/dto/create-cat.dto'),
{
CreateCatDto: {
enumWithDescription: {
enum: await import(
'./src/cats/dto/pagination-query.dto'
).then((f) => f.LettersEnum)
},
name: {
description: 'Name of the cat'
}
Expand Down
15 changes: 14 additions & 1 deletion lib/plugin/metadata-loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { METADATA_FACTORY_NAME } from './plugin-constants';

export class MetadataLoader {
private static readonly refreshHooks = new Array<() => void>();

static addRefreshHook(hook: () => void) {
return MetadataLoader.refreshHooks.unshift(hook);
}

async load(metadata: Record<string, any>) {
const pkgMetadata = metadata['@nestjs/swagger'];
if (!pkgMetadata) {
Expand All @@ -13,9 +19,12 @@ export class MetadataLoader {
if (controllers) {
await this.applyMetadata(controllers);
}
this.runHooks();
}

private async applyMetadata(meta: Record<string, any>) {
private async applyMetadata(
meta: Array<[Promise<unknown>, Record<string, any>]>
) {
const loadPromises = meta.map(async ([fileImport, fileMeta]) => {
const fileRef = await fileImport;
Object.keys(fileMeta).map((key) => {
Expand All @@ -25,4 +34,8 @@ export class MetadataLoader {
});
await Promise.all(loadPromises);
}

private runHooks() {
MetadataLoader.refreshHooks.forEach((hook) => hook());
}
}
37 changes: 24 additions & 13 deletions lib/type-helpers/intersection-type.helper.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Type } from '@nestjs/common';
import {
inheritTransformationMetadata,
inheritValidationMetadata,
inheritPropertyInitializers,
inheritTransformationMetadata,
inheritValidationMetadata
} from '@nestjs/mapped-types';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { MetadataLoader } from '../plugin/metadata-loader';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import { clonePluginMetadataFactory } from './mapped-types.utils';

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

clonePluginMetadataFactory(
IntersectionClassType as Type<unknown>,
classRef.prototype
);
function applyFields(fields: string[]) {
clonePluginMetadataFactory(
IntersectionClassType as Type<unknown>,
classRef.prototype
);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
propertyKey
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(IntersectionClassType.prototype, propertyKey);
});
}
applyFields(fields);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
propertyKey
MetadataLoader.addRefreshHook(() => {
const fields = modelPropertiesAccessor.getModelProperties(
classRef.prototype
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(IntersectionClassType.prototype, propertyKey);
applyFields(fields);
});
});

Expand Down
41 changes: 27 additions & 14 deletions lib/type-helpers/omit-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { omit } from 'lodash';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { MetadataLoader } from '../plugin/metadata-loader';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import { clonePluginMetadataFactory } from './mapped-types.utils';

Expand All @@ -15,7 +16,7 @@ const modelPropertiesAccessor = new ModelPropertiesAccessor();
export function OmitType<T, K extends keyof T>(
classRef: Type<T>,
keys: readonly K[]
): Type<Omit<T, typeof keys[number]>> {
): Type<Omit<T, (typeof keys)[number]>> {
const fields = modelPropertiesAccessor
.getModelProperties(classRef.prototype)
.filter((item) => !keys.includes(item as K));
Expand All @@ -31,20 +32,32 @@ export function OmitType<T, K extends keyof T>(
inheritValidationMetadata(classRef, OmitTypeClass, isInheritedPredicate);
inheritTransformationMetadata(classRef, OmitTypeClass, isInheritedPredicate);

clonePluginMetadataFactory(
OmitTypeClass as Type<unknown>,
classRef.prototype,
(metadata: Record<string, any>) => omit(metadata, keys)
);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
function applyFields(fields: string[]) {
clonePluginMetadataFactory(
OmitTypeClass as Type<unknown>,
classRef.prototype,
propertyKey
(metadata: Record<string, any>) => omit(metadata, keys)
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(OmitTypeClass.prototype, propertyKey);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
propertyKey
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(OmitTypeClass.prototype, propertyKey);
});
}
applyFields(fields);

MetadataLoader.addRefreshHook(() => {
const fields = modelPropertiesAccessor
.getModelProperties(classRef.prototype)
.filter((item) => !keys.includes(item as K));

applyFields(fields);
});
return OmitTypeClass as Type<Omit<T, typeof keys[number]>>;

return OmitTypeClass as Type<Omit<T, (typeof keys)[number]>>;
}
51 changes: 31 additions & 20 deletions lib/type-helpers/partial-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { mapValues } from 'lodash';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { MetadataLoader } from '../plugin/metadata-loader';
import { METADATA_FACTORY_NAME } from '../plugin/plugin-constants';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import { clonePluginMetadataFactory } from './mapped-types.utils';
Expand All @@ -25,27 +26,37 @@ export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
inheritValidationMetadata(classRef, PartialTypeClass);
inheritTransformationMetadata(classRef, PartialTypeClass);

clonePluginMetadataFactory(
PartialTypeClass as Type<unknown>,
classRef.prototype,
(metadata: Record<string, any>) =>
mapValues(metadata, (item) => ({ ...item, required: false }))
);

fields.forEach((key) => {
const metadata =
Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
key
) || {};

const decoratorFactory = ApiProperty({
...metadata,
required: false
function applyFields(fields: string[]) {
clonePluginMetadataFactory(
PartialTypeClass as Type<unknown>,
classRef.prototype,
(metadata: Record<string, any>) =>
mapValues(metadata, (item) => ({ ...item, required: false }))
);

fields.forEach((key) => {
const metadata =
Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
key
) || {};

const decoratorFactory = ApiProperty({
...metadata,
required: false
});
decoratorFactory(PartialTypeClass.prototype, key);
applyIsOptionalDecorator(PartialTypeClass, key);
});
decoratorFactory(PartialTypeClass.prototype, key);
applyIsOptionalDecorator(PartialTypeClass, key);
}
applyFields(fields);

MetadataLoader.addRefreshHook(() => {
const fields = modelPropertiesAccessor.getModelProperties(
classRef.prototype
);
applyFields(fields);
});

if (PartialTypeClass[METADATA_FACTORY_NAME]) {
Expand Down
40 changes: 26 additions & 14 deletions lib/type-helpers/pick-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { pick } from 'lodash';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
import { MetadataLoader } from '../plugin/metadata-loader';
import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import { clonePluginMetadataFactory } from './mapped-types.utils';

Expand All @@ -15,7 +16,7 @@ const modelPropertiesAccessor = new ModelPropertiesAccessor();
export function PickType<T, K extends keyof T>(
classRef: Type<T>,
keys: readonly K[]
): Type<Pick<T, typeof keys[number]>> {
): Type<Pick<T, (typeof keys)[number]>> {
const fields = modelPropertiesAccessor
.getModelProperties(classRef.prototype)
.filter((item) => keys.includes(item as K));
Expand All @@ -32,21 +33,32 @@ export function PickType<T, K extends keyof T>(
inheritValidationMetadata(classRef, PickTypeClass, isInheritedPredicate);
inheritTransformationMetadata(classRef, PickTypeClass, isInheritedPredicate);

clonePluginMetadataFactory(
PickTypeClass as Type<unknown>,
classRef.prototype,
(metadata: Record<string, any>) => pick(metadata, keys)
);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
function applyFields(fields: string[]) {
clonePluginMetadataFactory(
PickTypeClass as Type<unknown>,
classRef.prototype,
propertyKey
(metadata: Record<string, any>) => pick(metadata, keys)
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(PickTypeClass.prototype, propertyKey);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
propertyKey
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(PickTypeClass.prototype, propertyKey);
});
}
applyFields(fields);

MetadataLoader.addRefreshHook(() => {
const fields = modelPropertiesAccessor
.getModelProperties(classRef.prototype)
.filter((item) => keys.includes(item as K));

applyFields(fields);
});

return PickTypeClass as Type<Pick<T, typeof keys[number]>>;
return PickTypeClass as Type<Pick<T, (typeof keys)[number]>>;
}
Loading

0 comments on commit f3648b0

Please sign in to comment.