Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use GraphQL Tools for Federation GW #3054

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/apollo-federation-compatibility/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@
"start": "node ./dist/index.js"
},
"dependencies": {
"@apollo/subgraph": "^2.4.0",
"@graphql-tools/federation": "1.1.10",
"@graphql-yoga/plugin-apollo-inline-trace": "2.0.5",
"graphql": "16.6.0",
"graphql-tag": "2.12.6",
"graphql-yoga": "4.0.5"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions examples/apollo-federation-compatibility/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ type ProductDimension @shareable {
unit: String @inaccessible
}

extend type Query {
type Query @extends {
product(id: ID!): Product
deprecatedProduct(sku: String!, package: String!): DeprecatedProduct
@deprecated(reason: "Use product query instead")
}

extend type User @key(fields: "email") {
type User @key(fields: "email") @extends {
averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment")
email: ID! @external
name: String @override(from: "users")
Expand Down
15 changes: 8 additions & 7 deletions examples/apollo-federation-compatibility/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { readFileSync } from 'node:fs';
import { createServer } from 'node:http';
import { gql } from 'graphql-tag';
import { createYoga } from 'graphql-yoga';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { buildSubgraphSchema } from '@graphql-tools/federation';
import { useApolloInlineTrace } from '@graphql-yoga/plugin-apollo-inline-trace';
import { Inventory, Product, ProductResearch, Resolvers, User } from './resolvers-types';

Expand Down Expand Up @@ -59,7 +58,7 @@ const inventory: Inventory = {
const resolvers: Resolvers = {
Query: {
product(_: unknown, args: { id: string }) {
return products.find(p => p.id === args.id)! as unknown as Product;
return products.find(p => p.id === args.id) as unknown as Product;
},
deprecatedProduct: (_, args) => {
if (args.sku === deprecatedProduct.sku && args.package === deprecatedProduct.package) {
Expand All @@ -84,7 +83,9 @@ const resolvers: Resolvers = {
},
ProductResearch: {
__resolveReference: reference => {
return productResearch.find(p => reference.study.caseNumber === p.study.caseNumber)!;
return productResearch.find(
p => reference.study.caseNumber === p.study.caseNumber,
) as unknown as ProductResearch;
},
},
Product: {
Expand Down Expand Up @@ -138,7 +139,7 @@ const resolvers: Resolvers = {
name() {
return 'Jane Smith';
},
// @ts-expect-error
// @ts-expect-error (yearsOfEmployment is not in the type)
__resolveReference(userRef) {
const ref = userRef as User;
if (ref.email) {
Expand All @@ -151,7 +152,7 @@ const resolvers: Resolvers = {
user.totalProductsCreated = ref.totalProductsCreated;
}
if (ref.yearsOfEmployment) {
// @ts-expect-error
// @ts-expect-error (yearsOfEmployment is not in the type)
user.yearsOfEmployment = ref.yearsOfEmployment;
}
return user;
Expand All @@ -170,7 +171,7 @@ const resolvers: Resolvers = {
};

const yoga = createYoga({
schema: buildSubgraphSchema([{ typeDefs: gql(typeDefs), resolvers }]),
schema: buildSubgraphSchema({ typeDefs, resolvers }),
plugins: [useApolloInlineTrace()],
});

Expand Down
82 changes: 43 additions & 39 deletions examples/apollo-federation-compatibility/src/resolvers-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,67 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
export type RequireFields<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: NonNullable<T[P]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
_FieldSet: any;
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
_FieldSet: { input: any; output: any; }
};

export type CaseStudy = {
__typename?: 'CaseStudy';
caseNumber: Scalars['ID'];
description?: Maybe<Scalars['String']>;
caseNumber: Scalars['ID']['output'];
description?: Maybe<Scalars['String']['output']>;
};

export type DeprecatedProduct = {
__typename?: 'DeprecatedProduct';
createdBy?: Maybe<User>;
package: Scalars['String'];
reason?: Maybe<Scalars['String']>;
sku: Scalars['String'];
package: Scalars['String']['output'];
reason?: Maybe<Scalars['String']['output']>;
sku: Scalars['String']['output'];
};

export type Inventory = {
__typename?: 'Inventory';
deprecatedProducts: Array<DeprecatedProduct>;
id: Scalars['ID'];
id: Scalars['ID']['output'];
};

export type Product = {
__typename?: 'Product';
createdBy?: Maybe<User>;
dimensions?: Maybe<ProductDimension>;
id: Scalars['ID'];
notes?: Maybe<Scalars['String']>;
package?: Maybe<Scalars['String']>;
id: Scalars['ID']['output'];
notes?: Maybe<Scalars['String']['output']>;
package?: Maybe<Scalars['String']['output']>;
research: Array<ProductResearch>;
sku?: Maybe<Scalars['String']>;
sku?: Maybe<Scalars['String']['output']>;
variation?: Maybe<ProductVariation>;
};

export type ProductDimension = {
__typename?: 'ProductDimension';
size?: Maybe<Scalars['String']>;
unit?: Maybe<Scalars['String']>;
weight?: Maybe<Scalars['Float']>;
size?: Maybe<Scalars['String']['output']>;
unit?: Maybe<Scalars['String']['output']>;
weight?: Maybe<Scalars['Float']['output']>;
};

export type ProductResearch = {
__typename?: 'ProductResearch';
outcome?: Maybe<Scalars['String']>;
outcome?: Maybe<Scalars['String']['output']>;
study: CaseStudy;
};

export type ProductVariation = {
__typename?: 'ProductVariation';
id: Scalars['ID'];
id: Scalars['ID']['output'];
};

export type Query = {
Expand All @@ -74,22 +76,22 @@ export type Query = {


export type QueryDeprecatedProductArgs = {
package: Scalars['String'];
sku: Scalars['String'];
package: Scalars['String']['input'];
sku: Scalars['String']['input'];
};


export type QueryProductArgs = {
id: Scalars['ID'];
id: Scalars['ID']['input'];
};

export type User = {
__typename?: 'User';
averageProductsCreatedPerYear?: Maybe<Scalars['Int']>;
email: Scalars['ID'];
name?: Maybe<Scalars['String']>;
totalProductsCreated?: Maybe<Scalars['Int']>;
yearsOfEmployment: Scalars['Int'];
averageProductsCreatedPerYear?: Maybe<Scalars['Int']['output']>;
email: Scalars['ID']['output'];
name?: Maybe<Scalars['String']['output']>;
totalProductsCreated?: Maybe<Scalars['Int']['output']>;
yearsOfEmployment: Scalars['Int']['output'];
};


Expand Down Expand Up @@ -170,40 +172,42 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
info: GraphQLResolveInfo
) => TResult | Promise<TResult>;



/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = {
CaseStudy: ResolverTypeWrapper<CaseStudy>;
ID: ResolverTypeWrapper<Scalars['ID']>;
String: ResolverTypeWrapper<Scalars['String']>;
ID: ResolverTypeWrapper<Scalars['ID']['output']>;
String: ResolverTypeWrapper<Scalars['String']['output']>;
DeprecatedProduct: ResolverTypeWrapper<DeprecatedProduct>;
Inventory: ResolverTypeWrapper<Inventory>;
Product: ResolverTypeWrapper<Product>;
ProductDimension: ResolverTypeWrapper<ProductDimension>;
Float: ResolverTypeWrapper<Scalars['Float']>;
Float: ResolverTypeWrapper<Scalars['Float']['output']>;
ProductResearch: ResolverTypeWrapper<ProductResearch>;
ProductVariation: ResolverTypeWrapper<ProductVariation>;
Query: ResolverTypeWrapper<{}>;
User: ResolverTypeWrapper<User>;
Int: ResolverTypeWrapper<Scalars['Int']>;
Boolean: ResolverTypeWrapper<Scalars['Boolean']>;
Int: ResolverTypeWrapper<Scalars['Int']['output']>;
Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
};

/** Mapping between all available schema types and the resolvers parents */
export type ResolversParentTypes = {
CaseStudy: CaseStudy;
ID: Scalars['ID'];
String: Scalars['String'];
ID: Scalars['ID']['output'];
String: Scalars['String']['output'];
DeprecatedProduct: DeprecatedProduct;
Inventory: Inventory;
Product: Product;
ProductDimension: ProductDimension;
Float: Scalars['Float'];
Float: Scalars['Float']['output'];
ProductResearch: ProductResearch;
ProductVariation: ProductVariation;
Query: {};
User: User;
Int: Scalars['Int'];
Boolean: Scalars['Boolean'];
Int: Scalars['Int']['output'];
Boolean: Scalars['Boolean']['output'];
};

export type CustomDirectiveArgs = { };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { readFileSync } from 'node:fs';
import { createServer, Server } from 'node:http';
import { AddressInfo } from 'node:net';
import { GatewayConfig, IntrospectAndCompose } from '@apollo/gateway';
import { join } from 'node:path';
import { buildHTTPExecutor } from '@graphql-tools/executor-http';
import { fetch } from '@whatwg-node/fetch';
import { gateway } from '../gateway/gateway';
import { yoga as service1 } from '../service/yoga';
Expand All @@ -16,10 +18,9 @@ describe('apollo-federation example integration', () => {
await new Promise<void>(resolve => serviceServer.listen(0, resolve));
servicePort = (serviceServer.address() as AddressInfo).port;

const gatewayConfig: GatewayConfig = {
supergraphSdl: new IntrospectAndCompose({
subgraphs: [{ name: 'accounts', url: `http://localhost:${servicePort}/graphql` }],
}),
const gatewayConfig = {
supergraphSdl: readFileSync(join(__dirname, '../supergraph.graphql'), 'utf8'),
onExecutor: () => buildHTTPExecutor({ endpoint: `http://localhost:${servicePort}/graphql` }),
};

const gatewayService = await gateway(gatewayConfig);
Expand Down Expand Up @@ -54,10 +55,6 @@ describe('apollo-federation example integration', () => {
},
"errors": [
{
"extensions": {
"code": "DOWNSTREAM_SERVICE_ERROR",
"serviceName": "accounts",
},
"message": "This should throw.",
"path": [
"throw",
Expand Down
23 changes: 3 additions & 20 deletions examples/apollo-federation/gateway/gateway.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,13 @@
/* eslint-disable */
const { createYoga, maskError } = require('graphql-yoga');
const { ApolloGateway } = require('@apollo/gateway');
const { useApolloFederation } = require('@envelop/apollo-federation');
const { getStitchedSchemaFromSupergraphSdl } = require('@graphql-tools/federation');

module.exports.gateway = async function gateway(config) {
// Initialize the gateway
const gateway = new ApolloGateway(config);

// Make sure all services are loaded
await gateway.load();
const schema = getStitchedSchemaFromSupergraphSdl(config)

const yoga = createYoga({
plugins: [
useApolloFederation({
gateway,
}),
],
maskedErrors: {
maskError(error, message, isDev) {
// Note: it seems like the "useApolloFederation" plugin should do this by default?
if (error?.extensions?.code === 'DOWNSTREAM_SERVICE_ERROR') {
return error;
}
return maskError(error, message, isDev);
},
},
schema,
});

return yoga;
Expand Down
9 changes: 3 additions & 6 deletions examples/apollo-federation/gateway/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
/* eslint-disable */
const { createServer } = require('http');
const { gateway } = require('./gateway');
const { readFileSync } = require('fs');
const { join } = require('path');

async function main() {
const yoga = await gateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'accounts', url: 'http://localhost:4001/graphql' },
// ...additional subgraphs...
],
}),
supergraphSdl: readFileSync(join(__dirname, '../supergraph.graphql')).toString('utf-8'),
});

// Start the server and explore http://localhost:4000/graphql
Expand Down
3 changes: 1 addition & 2 deletions examples/apollo-federation/gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"start": "node index.js"
},
"dependencies": {
"@apollo/gateway": "2.4.7",
"@envelop/apollo-federation": "4.0.3",
"@graphql-tools/federation": "1.1.10",
"graphql": "16.6.0",
"graphql-yoga": "4.0.5"
}
Expand Down
3 changes: 2 additions & 1 deletion examples/apollo-federation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"concurrently": "^8.0.0"
},
"devDependencies": {
"@apollo/gateway": "2.4.7",
"@graphql-tools/executor-http": "1.0.2",
"@graphql-tools/federation": "1.1.10",
"@whatwg-node/fetch": "^0.9.0"
}
}
2 changes: 1 addition & 1 deletion examples/apollo-federation/service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "node index.js"
},
"dependencies": {
"@apollo/subgraph": "^2.4.0",
"@graphql-tools/federation": "1.1.10",
"graphql": "16.6.0",
"graphql-yoga": "4.0.5"
}
Expand Down
4 changes: 2 additions & 2 deletions examples/apollo-federation/service/yoga.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable */
const { parse, GraphQLError } = require('graphql');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const { buildSubgraphSchema } = require('@graphql-tools/federation');
const { createYoga } = require('graphql-yoga');

const typeDefs = parse(/* GraphQL */ `
Expand Down Expand Up @@ -32,5 +32,5 @@ const resolvers = {
};

module.exports.yoga = createYoga({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});