Skip to content

Commit 8d95fd9

Browse files
fix(graphql): apply external metadata file and override fields
1 parent d96c873 commit 8d95fd9

File tree

7 files changed

+129
-67
lines changed

7 files changed

+129
-67
lines changed

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

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

33
export class MetadataLoader {
4+
public static readonly refreshHooks = new Set<() => void>();
5+
46
async load(metadata: Record<string, any>) {
57
const pkgMetadata = metadata['@nestjs/graphql'];
68
if (!pkgMetadata) {
@@ -10,6 +12,8 @@ export class MetadataLoader {
1012
if (models) {
1113
await this.applyMetadata(models);
1214
}
15+
16+
MetadataLoader.refreshHooks.forEach((hook) => hook());
1317
}
1418

1519
private async applyMetadata(meta: Record<string, any>) {

packages/graphql/lib/schema-builder/storages/type-metadata.storage.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,12 @@ export class TypeMetadataStorageHost {
253253
);
254254
}
255255

256-
compileClassMetadata(metadata: ClassMetadata[]) {
256+
compileClassMetadata(
257+
metadata: ClassMetadata[],
258+
options?: { overrideFields?: boolean },
259+
) {
257260
metadata.forEach((item) => {
258-
if (!item.properties) {
261+
if (!item.properties || options?.overrideFields) {
259262
item.properties = this.getClassFieldsByPredicate(item);
260263
}
261264
if (!item.directives) {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import { ClassMetadata, PropertyMetadata } from '../metadata';
1313
import { LazyMetadataStorage } from '../storages/lazy-metadata.storage';
1414
import { TypeMetadataStorage } from '../storages/type-metadata.storage';
1515

16-
export function getFieldsAndDecoratorForType<T>(objType: Type<T>) {
16+
export function getFieldsAndDecoratorForType<T>(
17+
objType: Type<T>,
18+
options?: { overrideFields?: boolean },
19+
) {
1720
const classType = Reflect.getMetadata(CLASS_TYPE_METADATA, objType);
1821
if (!classType) {
1922
throw new UnableToFindFieldsError(objType.name);
@@ -27,7 +30,7 @@ export function getFieldsAndDecoratorForType<T>(objType: Type<T>) {
2730
getClassMetadataAndFactoryByTargetAndType(classType, objType);
2831

2932
TypeMetadataStorage.loadClassPluginMetadata([classMetadata]);
30-
TypeMetadataStorage.compileClassMetadata([classMetadata]);
33+
TypeMetadataStorage.compileClassMetadata([classMetadata], options);
3134

3235
let fields = classMetadata?.properties;
3336
if (!fields) {

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

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
} from '@nestjs/mapped-types';
88
import { Field } from '../decorators';
99
import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface';
10+
import { MetadataLoader } from '../plugin/metadata-loader';
11+
import { PropertyMetadata } from '../schema-builder/metadata';
1012
import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util';
1113
import { applyFieldDecorators } from './type-helpers.utils';
1214

@@ -37,20 +39,35 @@ export function IntersectionType<A, B>(
3739
inheritValidationMetadata(classBRef, IntersectionObjectType);
3840
inheritTransformationMetadata(classBRef, IntersectionObjectType);
3941

40-
fields.forEach((item) => {
41-
if (isFunction(item.typeFn)) {
42-
/**
43-
* Execute type function eagarly to update the type options object (before "clone" operation)
44-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
45-
*/
46-
item.typeFn();
47-
}
42+
function applyFields(fields: PropertyMetadata[]) {
43+
fields.forEach((item) => {
44+
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+
*/
49+
item.typeFn();
50+
}
51+
52+
Field(item.typeFn, { ...item.options })(
53+
IntersectionObjectType.prototype,
54+
item.name,
55+
);
56+
applyFieldDecorators(IntersectionObjectType, item);
57+
});
58+
}
59+
applyFields(fields);
60+
61+
MetadataLoader.refreshHooks.add(() => {
62+
const { fields: fieldsA } = getFieldsAndDecoratorForType(classARef, {
63+
overrideFields: true,
64+
});
65+
const { fields: fieldsB } = getFieldsAndDecoratorForType(classBRef, {
66+
overrideFields: true,
67+
});
68+
const fields = [...fieldsA, ...fieldsB];
4869

49-
Field(item.typeFn, { ...item.options })(
50-
IntersectionObjectType.prototype,
51-
item.name,
52-
);
53-
applyFieldDecorators(IntersectionObjectType, item);
70+
applyFields(fields);
5471
});
5572

5673
Object.defineProperty(IntersectionObjectType, 'name', {

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

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import {
77
} from '@nestjs/mapped-types';
88
import { Field } from '../decorators';
99
import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface';
10+
import { MetadataLoader } from '../plugin/metadata-loader';
11+
import { PropertyMetadata } from '../schema-builder/metadata';
1012
import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util';
1113
import { applyFieldDecorators } from './type-helpers.utils';
1214

1315
export function OmitType<T, K extends keyof T>(
1416
classRef: Type<T>,
1517
keys: readonly K[],
1618
decorator?: ClassDecoratorFactory,
17-
): Type<Omit<T, typeof keys[number]>> {
19+
): Type<Omit<T, (typeof keys)[number]>> {
1820
const { fields, decoratorFactory } = getFieldsAndDecoratorForType(classRef);
1921

2022
const isInheritedPredicate = (propertyKey: string) =>
@@ -33,22 +35,33 @@ export function OmitType<T, K extends keyof T>(
3335
inheritValidationMetadata(classRef, OmitObjectType, isInheritedPredicate);
3436
inheritTransformationMetadata(classRef, OmitObjectType, isInheritedPredicate);
3537

36-
fields
37-
.filter((item) => !keys.includes(item.name as K))
38-
.forEach((item) => {
39-
if (isFunction(item.typeFn)) {
40-
/**
41-
* Execute type function eagarly to update the type options object (before "clone" operation)
42-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
43-
*/
44-
item.typeFn();
45-
}
38+
function applyFields(fields: PropertyMetadata[]) {
39+
fields
40+
.filter((item) => !keys.includes(item.name as K))
41+
.forEach((item) => {
42+
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+
*/
47+
item.typeFn();
48+
}
4649

47-
Field(item.typeFn, { ...item.options })(
48-
OmitObjectType.prototype,
49-
item.name,
50-
);
51-
applyFieldDecorators(OmitObjectType, item);
50+
Field(item.typeFn, { ...item.options })(
51+
OmitObjectType.prototype,
52+
item.name,
53+
);
54+
applyFieldDecorators(OmitObjectType, item);
55+
});
56+
}
57+
applyFields(fields);
58+
59+
MetadataLoader.refreshHooks.add(() => {
60+
const { fields } = getFieldsAndDecoratorForType(classRef, {
61+
overrideFields: true,
5262
});
53-
return OmitObjectType as Type<Omit<T, typeof keys[number]>>;
63+
applyFields(fields);
64+
});
65+
66+
return OmitObjectType as Type<Omit<T, (typeof keys)[number]>>;
5467
}

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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
} from '@nestjs/mapped-types';
99
import { Field } from '../decorators';
1010
import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface';
11+
import { MetadataLoader } from '../plugin/metadata-loader';
1112
import { METADATA_FACTORY_NAME } from '../plugin/plugin-constants';
13+
import { PropertyMetadata } from '../schema-builder/metadata';
1214
import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util';
1315
import { applyFieldDecorators } from './type-helpers.utils';
1416

@@ -17,7 +19,6 @@ export function PartialType<T>(
1719
decorator?: ClassDecoratorFactory,
1820
): Type<Partial<T>> {
1921
const { fields, decoratorFactory } = getFieldsAndDecoratorForType(classRef);
20-
2122
abstract class PartialObjectType {
2223
constructor() {
2324
inheritPropertyInitializers(this, classRef);
@@ -32,21 +33,24 @@ export function PartialType<T>(
3233
inheritValidationMetadata(classRef, PartialObjectType);
3334
inheritTransformationMetadata(classRef, PartialObjectType);
3435

35-
fields.forEach((item) => {
36-
if (isFunction(item.typeFn)) {
37-
/**
38-
* Execute type function eagerly to update the type options object (before "clone" operation)
39-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
40-
*/
41-
item.typeFn();
42-
}
43-
Field(item.typeFn, { ...item.options, nullable: true })(
44-
PartialObjectType.prototype,
45-
item.name,
46-
);
47-
applyIsOptionalDecorator(PartialObjectType, item.name);
48-
applyFieldDecorators(PartialObjectType, item);
49-
});
36+
function applyFields(fields: PropertyMetadata[]) {
37+
fields.forEach((item) => {
38+
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+
*/
43+
item.typeFn();
44+
}
45+
Field(item.typeFn, { ...item.options, nullable: true })(
46+
PartialObjectType.prototype,
47+
item.name,
48+
);
49+
applyIsOptionalDecorator(PartialObjectType, item.name);
50+
applyFieldDecorators(PartialObjectType, item);
51+
});
52+
}
53+
applyFields(fields);
5054

5155
if (PartialObjectType[METADATA_FACTORY_NAME]) {
5256
const pluginFields = Object.keys(
@@ -56,6 +60,12 @@ export function PartialType<T>(
5660
applyIsOptionalDecorator(PartialObjectType, key),
5761
);
5862
}
63+
MetadataLoader.refreshHooks.add(() => {
64+
const { fields } = getFieldsAndDecoratorForType(classRef, {
65+
overrideFields: true,
66+
});
67+
applyFields(fields);
68+
});
5969

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

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

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import {
77
} from '@nestjs/mapped-types';
88
import { Field } from '../decorators';
99
import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface';
10+
import { MetadataLoader } from '../plugin/metadata-loader';
11+
import { PropertyMetadata } from '../schema-builder/metadata';
1012
import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util';
1113
import { applyFieldDecorators } from './type-helpers.utils';
1214

1315
export function PickType<T, K extends keyof T>(
1416
classRef: Type<T>,
1517
keys: readonly K[],
1618
decorator?: ClassDecoratorFactory,
17-
): Type<Pick<T, typeof keys[number]>> {
19+
): Type<Pick<T, (typeof keys)[number]>> {
1820
const { fields, decoratorFactory } = getFieldsAndDecoratorForType(classRef);
1921

2022
const isInheritedPredicate = (propertyKey: string) =>
@@ -34,22 +36,32 @@ export function PickType<T, K extends keyof T>(
3436
inheritValidationMetadata(classRef, PickObjectType, isInheritedPredicate);
3537
inheritTransformationMetadata(classRef, PickObjectType, isInheritedPredicate);
3638

37-
fields
38-
.filter((item) => keys.includes(item.name as K))
39-
.forEach((item) => {
40-
if (isFunction(item.typeFn)) {
41-
/**
42-
* Execute type function eagarly to update the type options object (before "clone" operation)
43-
* when the passed function (e.g., @Field(() => Type)) lazily returns an array.
44-
*/
45-
item.typeFn();
46-
}
39+
function applyFields(fields: PropertyMetadata[]) {
40+
fields
41+
.filter((item) => keys.includes(item.name as K))
42+
.forEach((item) => {
43+
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+
*/
48+
item.typeFn();
49+
}
4750

48-
Field(item.typeFn, { ...item.options })(
49-
PickObjectType.prototype,
50-
item.name,
51-
);
52-
applyFieldDecorators(PickObjectType, item);
51+
Field(item.typeFn, { ...item.options })(
52+
PickObjectType.prototype,
53+
item.name,
54+
);
55+
applyFieldDecorators(PickObjectType, item);
56+
});
57+
}
58+
applyFields(fields);
59+
60+
MetadataLoader.refreshHooks.add(() => {
61+
const { fields } = getFieldsAndDecoratorForType(classRef, {
62+
overrideFields: true,
5363
});
54-
return PickObjectType as Type<Pick<T, typeof keys[number]>>;
64+
applyFields(fields);
65+
});
66+
return PickObjectType as Type<Pick<T, (typeof keys)[number]>>;
5567
}

0 commit comments

Comments
 (0)