Skip to content

Commit 8b7a3eb

Browse files
Merge pull request #2337 from mattleff/fixes-2331
fix(code-first): Use base model field options with ResolveField()
2 parents 0610788 + 82a32be commit 8b7a3eb

File tree

6 files changed

+116
-29
lines changed

6 files changed

+116
-29
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ID, ResolveField, Resolver, Parent } from '@nestjs/graphql';
2+
import { Ingredient } from './models/ingredient';
3+
4+
@Resolver((of) => Ingredient)
5+
export class IngredientsResolver {
6+
@ResolveField(() => ID)
7+
id(@Parent() { id }: Ingredient): string {
8+
return id;
9+
}
10+
11+
@ResolveField()
12+
name(@Parent() { name }: Ingredient): string {
13+
return name;
14+
}
15+
16+
@ResolveField()
17+
baseName(@Parent() { name }: Ingredient): string {
18+
return name;
19+
}
20+
}
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
import { Field, ID, ObjectType } from '@nestjs/graphql';
2-
@ObjectType()
3-
export class Ingredient {
4-
@Field((type) => ID)
2+
3+
class NodeIngredient {
4+
// This does not apply since the class is not decorated with the @ObjectType decorator
5+
@Field((type) => ID, {
6+
defaultValue: 'Not applied',
7+
description: 'Not applied',
8+
deprecationReason: 'Not applied',
9+
})
510
id: string;
11+
}
12+
13+
@ObjectType({ isAbstract: true })
14+
class BaseIngredient extends NodeIngredient {
15+
@Field({
16+
defaultValue: 'default',
17+
deprecationReason: 'is deprecated',
18+
description: 'ingredient base name',
19+
nullable: true,
20+
})
21+
baseName: string;
22+
}
623

24+
@ObjectType()
25+
export class Ingredient extends BaseIngredient {
726
@Field({
827
defaultValue: 'default',
928
deprecationReason: 'is deprecated',
@@ -13,6 +32,7 @@ export class Ingredient {
1332
name: string;
1433

1534
constructor(ingredient: Partial<Ingredient>) {
35+
super();
1636
Object.assign(this, ingredient);
1737
}
1838
}

packages/apollo/tests/code-first/recipes/recipes.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { Module } from '@nestjs/common';
22
import { APP_FILTER } from '@nestjs/core';
33
import { UnauthorizedFilter } from '../common/filters/unauthorized.filter';
44
import { DateScalar } from '../common/scalars/date.scalar';
5+
import { IngredientsResolver } from './ingredients.resolver';
56
import { IRecipesResolver } from './irecipes.resolver';
67
import { RecipesResolver } from './recipes.resolver';
78
import { RecipesService } from './recipes.service';
89

910
@Module({
1011
providers: [
12+
IngredientsResolver,
1113
RecipesResolver,
1214
IRecipesResolver,
1315
RecipesService,

packages/apollo/tests/e2e/code-first-schema.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { DirectionsResolver } from '../code-first/directions/directions.resolver
1717
import { SampleOrphanedEnum } from '../code-first/enums/sample-orphaned.enum';
1818
import { AbstractResolver } from '../code-first/other/abstract.resolver';
1919
import { SampleOrphanedType } from '../code-first/other/sample-orphaned.type';
20+
import { IngredientsResolver } from '../code-first/recipes/ingredients.resolver';
2021
import { IRecipesResolver } from '../code-first/recipes/irecipes.resolver';
2122
import { Recipe } from '../code-first/recipes/models/recipe';
2223
import { RecipesResolver } from '../code-first/recipes/recipes.resolver';
@@ -48,6 +49,7 @@ describe('Code-first - schema factory', () => {
4849
beforeAll(async () => {
4950
schema = await schemaFactory.create(
5051
[
52+
IngredientsResolver,
5153
RecipesResolver,
5254
DirectionsResolver,
5355
AbstractResolver,
@@ -212,6 +214,18 @@ describe('Code-first - schema factory', () => {
212214
ofType: null,
213215
},
214216
},
217+
{
218+
args: [],
219+
deprecationReason: 'is deprecated',
220+
description: 'ingredient base name',
221+
isDeprecated: true,
222+
name: 'baseName',
223+
type: {
224+
kind: TypeKind.SCALAR,
225+
name: 'String',
226+
ofType: null,
227+
},
228+
},
215229
]),
216230
}),
217231
);

packages/apollo/tests/utils/printed-schema.snapshot.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,19 @@ type SampleOrphanedType {
4040
averageRating: Float!
4141
}
4242
43-
type Category {
44-
name: String!
45-
description: String!
46-
tags: [String!]!
47-
}
48-
4943
type Ingredient {
50-
id: ID!
44+
"""ingredient base name"""
45+
baseName: String @deprecated(reason: "is deprecated")
5146
5247
"""ingredient name"""
5348
name: String @deprecated(reason: "is deprecated")
49+
id: ID!
50+
}
51+
52+
type Category {
53+
name: String!
54+
description: String!
55+
tags: [String!]!
5456
}
5557
5658
"""orphaned enum"""
@@ -141,6 +143,8 @@ interface IRecipe {
141143
}
142144
143145
type Ingredient {
146+
"""ingredient base name"""
147+
baseName: String @deprecated(reason: "is deprecated")
144148
id: ID!
145149
146150
"""ingredient name"""

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

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -338,24 +338,8 @@ export class TypeMetadataStorageHost {
338338
}
339339

340340
private compileExternalFieldResolverMetadata(item: FieldResolverMetadata) {
341-
const objectTypeRef = this.metadataByTargetCollection
342-
.get(item.target)
343-
.resolver.typeFn();
344-
345-
const objectOrInterfaceTypeMetadata =
346-
this.metadataByTargetCollection.get(objectTypeRef).objectType ||
347-
this.metadataByTargetCollection.get(objectTypeRef).interface;
348-
349-
if (!objectOrInterfaceTypeMetadata) {
350-
throw new CannotDetermineHostTypeError(
351-
item.schemaName,
352-
objectTypeRef?.name,
353-
);
354-
}
355-
const objectOrInterfaceTypeField =
356-
objectOrInterfaceTypeMetadata.properties.find(
357-
(fieldDef) => fieldDef.name === item.methodName,
358-
);
341+
const [target, objectOrInterfaceTypeMetadata, objectOrInterfaceTypeField] =
342+
this.findModelFieldMetadata(item);
359343
if (!objectOrInterfaceTypeField) {
360344
if (!item.typeFn || !item.typeOptions) {
361345
throw new UndefinedTypeError(item.target.name, item.methodName);
@@ -366,7 +350,7 @@ export class TypeMetadataStorageHost {
366350
deprecationReason: item.deprecationReason,
367351
description: item.description,
368352
typeFn: item.typeFn,
369-
target: objectTypeRef,
353+
target,
370354
options: item.typeOptions,
371355
methodArgs: item.methodArgs,
372356
directives: item.directives,
@@ -394,6 +378,49 @@ export class TypeMetadataStorageHost {
394378
}
395379
}
396380

381+
private findModelFieldMetadata(
382+
item: FieldResolverMetadata,
383+
): [Function, ClassMetadata, PropertyMetadata | undefined] {
384+
let objectTypeRef = this.metadataByTargetCollection
385+
.get(item.target)
386+
.resolver.typeFn();
387+
const getTypeMetadata = (target: any) => {
388+
const metadata = this.metadataByTargetCollection.get(target);
389+
return metadata.objectType || metadata.interface;
390+
};
391+
let objectOrInterfaceTypeMetadata = getTypeMetadata(objectTypeRef);
392+
if (!objectOrInterfaceTypeMetadata) {
393+
throw new CannotDetermineHostTypeError(
394+
item.schemaName,
395+
objectTypeRef?.name,
396+
);
397+
}
398+
let objectOrInterfaceTypeField =
399+
objectOrInterfaceTypeMetadata.properties.find(
400+
(fieldDef) => fieldDef.name === item.methodName,
401+
);
402+
for (
403+
let _objectTypeRef = objectTypeRef;
404+
!objectOrInterfaceTypeField && _objectTypeRef?.prototype;
405+
_objectTypeRef = Object.getPrototypeOf(_objectTypeRef)
406+
) {
407+
const possibleTypeMetadata = getTypeMetadata(_objectTypeRef);
408+
objectOrInterfaceTypeField = possibleTypeMetadata?.properties.find(
409+
(fieldDef) => fieldDef.name === item.methodName,
410+
);
411+
if (objectOrInterfaceTypeField) {
412+
objectTypeRef = _objectTypeRef;
413+
objectOrInterfaceTypeMetadata = possibleTypeMetadata;
414+
break;
415+
}
416+
}
417+
return [
418+
objectTypeRef,
419+
objectOrInterfaceTypeMetadata,
420+
objectOrInterfaceTypeField,
421+
];
422+
}
423+
397424
private compileExtendedResolversMetadata() {
398425
this.metadataByTargetCollection.all.resolver.forEach((item) => {
399426
let parentClass = Object.getPrototypeOf(item.target);

0 commit comments

Comments
 (0)