Skip to content

Commit c858262

Browse files
refactor(graphql): refactor metadata loader, add missing tests
1 parent 2b7109e commit c858262

13 files changed

+249
-118
lines changed

packages/graphql/lib/plugin/metadata-loader.ts

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

33
export class MetadataLoader {
4-
public static readonly refreshHooks = new Set<() => void>();
4+
private static readonly refreshHooks = new Array<() => void>();
5+
6+
static addRefreshHook(hook: () => void) {
7+
return MetadataLoader.refreshHooks.unshift(hook);
8+
}
59

610
async load(metadata: Record<string, any>) {
711
const pkgMetadata = metadata['@nestjs/graphql'];
@@ -12,11 +16,16 @@ export class MetadataLoader {
1216
if (models) {
1317
await this.applyMetadata(models);
1418
}
19+
this.runHooks();
20+
}
1521

22+
private runHooks() {
1623
MetadataLoader.refreshHooks.forEach((hook) => hook());
1724
}
1825

19-
private async applyMetadata(meta: Record<string, any>) {
26+
private async applyMetadata(
27+
meta: Array<[Promise<unknown>, Record<string, any>]>,
28+
) {
2029
const loadPromises = meta.map(async ([fileImport, fileMeta]) => {
2130
const fileRef = await fileImport;
2231
Object.keys(fileMeta).map((key) => {

packages/graphql/lib/schema-builder/utils/get-fields-and-decorator.util.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function getFieldsAndDecoratorForType<T>(
3636
if (!fields) {
3737
throw new UnableToFindFieldsError(objType.name);
3838
}
39-
fields = inheritClassFields(objType, fields);
39+
fields = inheritClassFields(objType, fields, options);
4040

4141
return {
4242
fields,
@@ -82,6 +82,7 @@ function getClassMetadataAndFactoryByTargetAndType(
8282
function inheritClassFields(
8383
objType: Type<unknown>,
8484
fields: PropertyMetadata[],
85+
options?: { overrideFields?: boolean },
8586
) {
8687
try {
8788
const parentClass = Object.getPrototypeOf(objType);
@@ -90,8 +91,13 @@ function inheritClassFields(
9091
}
9192
const { fields: parentFields } = getFieldsAndDecoratorForType(
9293
parentClass as Type<unknown>,
94+
options,
95+
);
96+
return inheritClassFields(
97+
parentClass,
98+
[...parentFields, ...fields],
99+
options,
93100
);
94-
return inheritClassFields(parentClass, [...parentFields, ...fields]);
95101
} catch (err) {
96102
return fields;
97103
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,8 @@ export function IntersectionType<A, B>(
4242
function applyFields(fields: PropertyMetadata[]) {
4343
fields.forEach((item) => {
4444
if (isFunction(item.typeFn)) {
45-
/**
46-
* Execute type function eagarly to update the type options object (before "clone" operation)
47-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
48-
*/
45+
// Execute type function eagerly to update the type options object (before "clone" operation)
46+
// when the passed function (e.g., @Field(() => Type)) lazily returns an array.
4947
item.typeFn();
5048
}
5149

@@ -58,7 +56,9 @@ export function IntersectionType<A, B>(
5856
}
5957
applyFields(fields);
6058

61-
MetadataLoader.refreshHooks.add(() => {
59+
// Register a refresh hook to update the fields when the serialized metadata
60+
// is loaded from file.
61+
MetadataLoader.addRefreshHook(() => {
6262
const { fields: fieldsA } = getFieldsAndDecoratorForType(classARef, {
6363
overrideFields: true,
6464
});

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ export function OmitType<T, K extends keyof T>(
4040
.filter((item) => !keys.includes(item.name as K))
4141
.forEach((item) => {
4242
if (isFunction(item.typeFn)) {
43-
/**
44-
* Execute type function eagarly to update the type options object (before "clone" operation)
45-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
46-
*/
43+
// Execute type function eagerly to update the type options object (before "clone" operation)
44+
// when the passed function (e.g., @Field(() => Type)) lazily returns an array.
4745
item.typeFn();
4846
}
4947

@@ -56,7 +54,9 @@ export function OmitType<T, K extends keyof T>(
5654
}
5755
applyFields(fields);
5856

59-
MetadataLoader.refreshHooks.add(() => {
57+
// Register a refresh hook to update the fields when the serialized metadata
58+
// is loaded from file.
59+
MetadataLoader.addRefreshHook(() => {
6060
const { fields } = getFieldsAndDecoratorForType(classRef, {
6161
overrideFields: true,
6262
});

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,8 @@ export function PartialType<T>(
3636
function applyFields(fields: PropertyMetadata[]) {
3737
fields.forEach((item) => {
3838
if (isFunction(item.typeFn)) {
39-
/**
40-
* Execute type function eagerly to update the type options object (before "clone" operation)
41-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
42-
*/
39+
// Execute type function eagerly to update the type options object (before "clone" operation)
40+
// when the passed function (e.g., @Field(() => Type)) lazily returns an array.
4341
item.typeFn();
4442
}
4543
Field(item.typeFn, { ...item.options, nullable: true })(
@@ -52,6 +50,15 @@ export function PartialType<T>(
5250
}
5351
applyFields(fields);
5452

53+
// Register a refresh hook to update the fields when the serialized metadata
54+
// is loaded from file.
55+
MetadataLoader.addRefreshHook(() => {
56+
const { fields } = getFieldsAndDecoratorForType(classRef, {
57+
overrideFields: true,
58+
});
59+
applyFields(fields);
60+
});
61+
5562
if (PartialObjectType[METADATA_FACTORY_NAME]) {
5663
const pluginFields = Object.keys(
5764
PartialObjectType[METADATA_FACTORY_NAME](),
@@ -60,12 +67,6 @@ export function PartialType<T>(
6067
applyIsOptionalDecorator(PartialObjectType, key),
6168
);
6269
}
63-
MetadataLoader.refreshHooks.add(() => {
64-
const { fields } = getFieldsAndDecoratorForType(classRef, {
65-
overrideFields: true,
66-
});
67-
applyFields(fields);
68-
});
6970

7071
Object.defineProperty(PartialObjectType, 'name', {
7172
value: `Partial${classRef.name}`,

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,8 @@ export function PickType<T, K extends keyof T>(
4141
.filter((item) => keys.includes(item.name as K))
4242
.forEach((item) => {
4343
if (isFunction(item.typeFn)) {
44-
/**
45-
* Execute type function eagarly to update the type options object (before "clone" operation)
46-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
47-
*/
44+
// Execute type function eagerly to update the type options object (before "clone" operation)
45+
// when the passed function (e.g., @Field(() => Type)) lazily returns an array.
4846
item.typeFn();
4947
}
5048

@@ -57,11 +55,14 @@ export function PickType<T, K extends keyof T>(
5755
}
5856
applyFields(fields);
5957

60-
MetadataLoader.refreshHooks.add(() => {
58+
// Register a refresh hook to update the fields when the serialized metadata
59+
// is loaded from file.
60+
MetadataLoader.addRefreshHook(() => {
6161
const { fields } = getFieldsAndDecoratorForType(classRef, {
6262
overrideFields: true,
6363
});
6464
applyFields(fields);
6565
});
66+
6667
return PickObjectType as Type<Pick<T, (typeof keys)[number]>>;
6768
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Directive, Extensions, Field, ObjectType } from '../../../../lib/decorators';
2+
3+
@ObjectType({ isAbstract: true })
4+
export abstract class BaseType {
5+
@Field()
6+
@Directive('@upper')
7+
@Extensions({ extension: true })
8+
id: string;
9+
10+
@Field()
11+
createdAt: Date;
12+
13+
@Field()
14+
updatedAt: Date;
15+
16+
meta: string;
17+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Transform } from 'class-transformer';
2+
import { IsBoolean, MinLength } from 'class-validator';
3+
import { Directive, Extensions, Field, ObjectType } from '../../../../lib/decorators';
4+
5+
@ObjectType()
6+
export class CreateUserDto {
7+
@Transform((str) => str + '_transformed')
8+
@MinLength(10)
9+
@Field({ nullable: true })
10+
@Directive('@upper')
11+
login: string;
12+
13+
@Transform((str) => str + '_transformed')
14+
@MinLength(10)
15+
@Field()
16+
@Directive('@upper')
17+
@Extensions({ extension: true })
18+
password: string;
19+
20+
@Field({ name: 'id' })
21+
@Extensions({ extension: true })
22+
_id: string;
23+
24+
@IsBoolean()
25+
active: boolean;
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export const SERIALIZED_METADATA = {
2+
'@nestjs/graphql': {
3+
models: [
4+
[
5+
import('./base-type.fixture'),
6+
{
7+
BaseType: {
8+
meta: {
9+
type: () => String,
10+
},
11+
},
12+
},
13+
],
14+
[
15+
import('./create-user-dto.fixture'),
16+
{
17+
CreateUserDto: {
18+
active: {
19+
type: () => Boolean,
20+
},
21+
},
22+
},
23+
],
24+
],
25+
},
26+
}

packages/graphql/tests/plugin/type-helpers/omit-type.helper.spec.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,8 @@
1-
import { Transform } from 'class-transformer';
2-
import { MinLength } from 'class-validator';
3-
import {
4-
Directive,
5-
Extensions,
6-
Field,
7-
ObjectType,
8-
} from '../../../lib/decorators';
91
import { getFieldsAndDecoratorForType } from '../../../lib/schema-builder/utils/get-fields-and-decorator.util';
102
import { OmitType } from '../../../lib/type-helpers';
3+
import { CreateUserDto } from './fixtures/create-user-dto.fixture';
114

125
describe('OmitType', () => {
13-
@ObjectType()
14-
class CreateUserDto {
15-
@MinLength(10)
16-
@Field({ nullable: true })
17-
login: string;
18-
19-
@Transform((str) => str + '_transformed')
20-
@MinLength(10)
21-
@Field()
22-
@Directive('@upper')
23-
@Extensions({ extension: true })
24-
password: string;
25-
26-
@Field({ name: 'id' })
27-
_id: string;
28-
}
29-
306
class UpdateUserDto extends OmitType(CreateUserDto, ['login', '_id']) {}
317

328
it('should inherit all fields except for "login" and "_id"', () => {

0 commit comments

Comments
 (0)