Skip to content

Commit

Permalink
fix(batch-delegate): respect the parent type name (#5998)
Browse files Browse the repository at this point in the history
* fix(batch-delegate): respect the parent type name for the batch delegation

* Use returnType

* Add tests

* Fix test
  • Loading branch information
ardatan committed Apr 19, 2024
1 parent 6cf507f commit 9eee255
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .changeset/tricky-mice-crash.md
@@ -0,0 +1,5 @@
---
"@graphql-tools/batch-delegate": patch
---

Add parentType name to the batch delegation key
5 changes: 5 additions & 0 deletions packages/batch-delegate/src/getLoader.ts
Expand Up @@ -87,6 +87,11 @@ export function getLoader<K = any, V = any, C = K>(

let cacheKey = fieldName;

if (info.returnType) {
const namedType = getNamedType(info.returnType);
cacheKey += '@' + namedType.name;
}

if (selectionSet != null) {
cacheKey += memoizedPrint(selectionSet);
}
Expand Down
@@ -0,0 +1,18 @@
type Query {
discounts(first: Int = 5): [Discount]
}

extend type Product @key(fields: "upc") {
upc: String! @external
discounts: [Discount!]!
}

extend type Category @key(fields: "id") {
id: ID! @external
discounts: [Discount!]!
}

type Discount @key(fields: "id") {
id: ID!
discount: Int!
}
42 changes: 42 additions & 0 deletions packages/federation/test/fixtures/gateway/discount/index.ts
@@ -0,0 +1,42 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { IResolvers } from '@graphql-tools/utils';

export const typeDefs = readFileSync(join(__dirname, './discount.graphql'), 'utf8');

export const resolvers: IResolvers = {
Product: {
__resolveReference(object) {
return {
...object,
discounts,
};
},
},
Category: {
__resolveReference(object) {
return {
...object,
discounts,
};
},
},
Discount: {
__resolveReference(object) {
return {
...object,
...discounts.find(discount => discount.id === object.id),
};
},
},
Query: {
discounts(_, args) {
return discounts.slice(0, args.first);
},
},
};
const discounts = [
{ id: '1', discount: 10 },
{ id: '2', discount: 20 },
{ id: '3', discount: 30 },
];
36 changes: 36 additions & 0 deletions packages/federation/test/fixtures/gateway/products/index.ts
Expand Up @@ -6,24 +6,52 @@ export const typeDefs = readFileSync(join(__dirname, './products.graphql'), 'utf

const listSize = parseInt(process.env['PRODUCTS_SIZE'] || '3');

const categories = [
{
id: 'c_1',
name: 'Furniture',
},
{
id: 'c_2',
name: 'Kitchen',
},
];

const definedProducts = [
{
upc: '1',
name: 'Table',
price: 899,
weight: 100,
categories: [categories[0]],
},
{
upc: '2',
name: 'Couch',
price: 1299,
weight: 1000,
categories: [categories[0]],
},
{
upc: '3',
name: 'Chair',
price: 54,
weight: 50,
categories: [categories[0]],
},
{
upc: '4',
name: 'Knife',
price: 54,
weight: 50,
categories: [categories[1]],
},
{
id: 'p_5',
name: 'Spoon',
price: 54,
weight: 50,
categories: [categories[1]],
},
];
const products = [...Array(listSize)].map((_, index) => definedProducts[index % 3]);
Expand All @@ -34,6 +62,14 @@ export const resolvers: IResolvers = {
return products.find(product => product.upc === object.upc);
},
},
Category: {
__resolveReference(object) {
return {
...object,
...categories.find(category => category.id === object.id),
};
},
},
Query: {
topProducts(_, args) {
return args.first ? products.slice(0, args.first) : products;
Expand Down
Expand Up @@ -7,4 +7,10 @@ type Product @key(fields: "upc") {
name: String
price: Int
weight: Int
categories: [Category!]!
}

type Category @key(fields: "id") {
id: ID!
name: String!
}
129 changes: 62 additions & 67 deletions packages/federation/test/fixtures/gateway/supergraph.graphql
@@ -1,103 +1,98 @@
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) {
@core(feature: "https://specs.apollo.dev/core/v0.2")
@core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION) {
query: Query
}

directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA

directive @join__field(
graph: join__Graph
requires: join__FieldSet
provides: join__FieldSet
type: String
external: Boolean
override: String
usedOverridden: Boolean
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
requires: join__FieldSet
) on FIELD_DEFINITION

directive @join__graph(name: String!, url: String!) on ENUM_VALUE

directive @join__implements(
graph: join__Graph!
interface: String!
) repeatable on OBJECT | INTERFACE

directive @join__type(
graph: join__Graph!
key: join__FieldSet
extension: Boolean! = false
resolvable: Boolean! = true
isInterfaceObject: Boolean! = false
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR

directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION

directive @link(
url: String
as: String
for: link__Purpose
import: [link__Import]
) repeatable on SCHEMA
directive @join__owner(graph: join__Graph!) on INTERFACE | OBJECT

scalar join__FieldSet
directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on INTERFACE | OBJECT

enum join__Graph {
accounts @join__graph(name: "accounts", url: "http://accounts:4001/graphql")
inventory @join__graph(name: "inventory", url: "http://inventory:4002/graphql")
products @join__graph(name: "products", url: "http://products:4003/graphql")
reviews @join__graph(name: "reviews", url: "http://reviews:4004/graphql")
type Category
@join__owner(graph: products)
@join__type(graph: products, key: "id")
@join__type(graph: discount, key: "id") {
discounts: [Discount!]! @join__field(graph: discount)
id: ID! @join__field(graph: products)
name: String! @join__field(graph: products)
}

scalar link__Import

enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY

"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
type Discount @join__owner(graph: discount) @join__type(graph: discount, key: "id") {
discount: Int! @join__field(graph: discount)
id: ID! @join__field(graph: discount)
}

type Product
@join__type(graph: inventory, key: "upc")
@join__owner(graph: products)
@join__type(graph: products, key: "upc")
@join__type(graph: discount, key: "upc")
@join__type(graph: inventory, key: "upc")
@join__type(graph: reviews, key: "upc") {
upc: String!
weight: Int @join__field(graph: inventory, external: true) @join__field(graph: products)
price: Int @join__field(graph: inventory, external: true) @join__field(graph: products)
categories: [Category!]! @join__field(graph: products)
discounts: [Discount!]! @join__field(graph: discount)
inStock: Boolean @join__field(graph: inventory)
shippingEstimate: Int @join__field(graph: inventory, requires: "price weight")
name: String @join__field(graph: products)
price: Int @join__field(graph: products)
reviews: [Review] @join__field(graph: reviews)
shippingEstimate: Int @join__field(graph: inventory, requires: "price weight")
upc: String! @join__field(graph: products)
weight: Int @join__field(graph: products)
}

type Query
@join__type(graph: accounts)
@join__type(graph: inventory)
@join__type(graph: products)
@join__type(graph: reviews) {
type Query {
discounts(first: Int = 5): [Discount] @join__field(graph: discount)
me: User @join__field(graph: accounts)
users: [User] @join__field(graph: accounts)
topProducts(first: Int): [Product] @join__field(graph: products)
users: [User] @join__field(graph: accounts)
}

type Review @join__type(graph: reviews, key: "id") {
id: ID!
body: String
product: Product
type Review @join__owner(graph: reviews) @join__type(graph: reviews, key: "id") {
author: User @join__field(graph: reviews, provides: "username")
body: String @join__field(graph: reviews)
id: ID! @join__field(graph: reviews)
product: Product @join__field(graph: reviews)
}

type User @join__type(graph: accounts, key: "id") @join__type(graph: reviews, key: "id") {
id: ID!
name: String @join__field(graph: accounts)
username: String @join__field(graph: accounts) @join__field(graph: reviews, external: true)
type User
@join__owner(graph: accounts)
@join__type(graph: accounts, key: "id")
@join__type(graph: reviews, key: "id") {
birthDate: String @join__field(graph: accounts)
id: ID! @join__field(graph: accounts)
name: String @join__field(graph: accounts)
numberOfReviews: Int @join__field(graph: reviews)
reviews: [Review] @join__field(graph: reviews)
username: String @join__field(graph: accounts)
}

enum core__Purpose {
"""
`EXECUTION` features provide metadata necessary to for operation execution.
"""
EXECUTION

"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
}

scalar join__FieldSet

enum join__Graph {
accounts @join__graph(name: "accounts", url: "http://www.accounts.com")
discount @join__graph(name: "discount", url: "http://www.discount.com")
inventory @join__graph(name: "inventory", url: "http://www.inventory.com")
products @join__graph(name: "products", url: "http://www.products.com")
reviews @join__graph(name: "reviews", url: "http://www.reviews.com")
}
4 changes: 4 additions & 0 deletions packages/federation/test/fixtures/gateway/supergraph.yaml
Expand Up @@ -15,3 +15,7 @@ subgraphs:
routing_url: http://www.inventory.com
schema:
file: ./inventory/inventory.graphql
discount:
routing_url: http://www.discount.com
schema:
file: ./discount/discount.graphql

0 comments on commit 9eee255

Please sign in to comment.