diff --git a/examples/federation-example/package.json b/examples/federation-example/package.json index 9b166ce187b57..0b160660bb82e 100644 --- a/examples/federation-example/package.json +++ b/examples/federation-example/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "apollo-server": "3.11.1", + "apollo-server-caching": "3.3.0", "concurrently": "7.6.0", "delay-cli": "2.0.0", "jest": "29.4.2", diff --git a/examples/federation-example/services/accounts/index.ts b/examples/federation-example/services/accounts/index.ts old mode 100755 new mode 100644 index 7700b7ee63a0a..21bbd774848c5 --- a/examples/federation-example/services/accounts/index.ts +++ b/examples/federation-example/services/accounts/index.ts @@ -1,58 +1,6 @@ -import { ApolloServer, gql } from 'apollo-server'; +import { accountsServer } from './server'; -const typeDefs = gql` - type Query { - me: User - user(id: ID!): User - users: [User] - } - - type User { - id: ID! - name: String - username: String - } -`; - -const resolvers = { - Query: { - me(_root, _args, context) { - return context.users[0]; - }, - users(_root, _args, context) { - return context.users; - }, - user(_root, args, context) { - return context.users.find(user => user.id === args.id); - }, - }, -}; - -const server = new ApolloServer({ - typeDefs, - resolvers, - context: { - users: [ - { - id: '1', - name: 'Ada Lovelace', - birthDate: '1815-12-10', - username: '@ada', - }, - { - id: '2', - name: 'Alan Turing', - birthDate: '1912-06-23', - username: '@complete', - }, - ], - }, +accountsServer().catch(error => { + console.error(error); + process.exit(1); }); - -export const accountsServer = () => - server.listen({ port: 9871 }).then(({ url }) => { - if (!process.env.CI) { - console.log(`🚀 Server ready at ${url}`); - } - return server; - }); diff --git a/examples/federation-example/services/accounts/server.ts b/examples/federation-example/services/accounts/server.ts new file mode 100755 index 0000000000000..7700b7ee63a0a --- /dev/null +++ b/examples/federation-example/services/accounts/server.ts @@ -0,0 +1,58 @@ +import { ApolloServer, gql } from 'apollo-server'; + +const typeDefs = gql` + type Query { + me: User + user(id: ID!): User + users: [User] + } + + type User { + id: ID! + name: String + username: String + } +`; + +const resolvers = { + Query: { + me(_root, _args, context) { + return context.users[0]; + }, + users(_root, _args, context) { + return context.users; + }, + user(_root, args, context) { + return context.users.find(user => user.id === args.id); + }, + }, +}; + +const server = new ApolloServer({ + typeDefs, + resolvers, + context: { + users: [ + { + id: '1', + name: 'Ada Lovelace', + birthDate: '1815-12-10', + username: '@ada', + }, + { + id: '2', + name: 'Alan Turing', + birthDate: '1912-06-23', + username: '@complete', + }, + ], + }, +}); + +export const accountsServer = () => + server.listen({ port: 9871 }).then(({ url }) => { + if (!process.env.CI) { + console.log(`🚀 Server ready at ${url}`); + } + return server; + }); diff --git a/examples/federation-example/services/inventory/index.ts b/examples/federation-example/services/inventory/index.ts old mode 100755 new mode 100644 index 34e2e5cfbb7b3..e2baee7b35f0c --- a/examples/federation-example/services/inventory/index.ts +++ b/examples/federation-example/services/inventory/index.ts @@ -1,52 +1,6 @@ -import { ApolloServer, gql } from 'apollo-server'; -import { buildSubgraphSchema } from '@apollo/subgraph'; +import { inventoryServer } from './server'; -const typeDefs = gql` - extend type Product @key(fields: "upc") { - upc: String! @external - weight: Int @external - price: Int @external - inStock: Boolean - shippingEstimate: Int @requires(fields: "price weight") - } -`; - -const resolvers = { - Product: { - __resolveReference(object) { - return { - ...object, - ...inventory.find(product => product.upc === object.upc), - }; - }, - shippingEstimate(object) { - // free for expensive items - if (object.price > 1000) return 0; - // estimate is based on weight - return object.weight * 0.5; - }, - }, -}; - -const server = new ApolloServer({ - schema: buildSubgraphSchema([ - { - typeDefs, - resolvers, - }, - ]), +inventoryServer().catch(error => { + console.error(error); + process.exit(1); }); - -export const inventoryServer = () => - server.listen({ port: 9872 }).then(({ url }) => { - if (!process.env.CI) { - console.log(`🚀 Server ready at ${url}`); - } - return server; - }); - -const inventory = [ - { upc: '1', inStock: true }, - { upc: '2', inStock: false }, - { upc: '3', inStock: true }, -]; diff --git a/examples/federation-example/services/inventory/server.ts b/examples/federation-example/services/inventory/server.ts new file mode 100755 index 0000000000000..34e2e5cfbb7b3 --- /dev/null +++ b/examples/federation-example/services/inventory/server.ts @@ -0,0 +1,52 @@ +import { ApolloServer, gql } from 'apollo-server'; +import { buildSubgraphSchema } from '@apollo/subgraph'; + +const typeDefs = gql` + extend type Product @key(fields: "upc") { + upc: String! @external + weight: Int @external + price: Int @external + inStock: Boolean + shippingEstimate: Int @requires(fields: "price weight") + } +`; + +const resolvers = { + Product: { + __resolveReference(object) { + return { + ...object, + ...inventory.find(product => product.upc === object.upc), + }; + }, + shippingEstimate(object) { + // free for expensive items + if (object.price > 1000) return 0; + // estimate is based on weight + return object.weight * 0.5; + }, + }, +}; + +const server = new ApolloServer({ + schema: buildSubgraphSchema([ + { + typeDefs, + resolvers, + }, + ]), +}); + +export const inventoryServer = () => + server.listen({ port: 9872 }).then(({ url }) => { + if (!process.env.CI) { + console.log(`🚀 Server ready at ${url}`); + } + return server; + }); + +const inventory = [ + { upc: '1', inStock: true }, + { upc: '2', inStock: false }, + { upc: '3', inStock: true }, +]; diff --git a/examples/federation-example/services/products/index.ts b/examples/federation-example/services/products/index.ts old mode 100755 new mode 100644 index 362f54564bdb8..1e31cf916ef26 --- a/examples/federation-example/services/products/index.ts +++ b/examples/federation-example/services/products/index.ts @@ -1,69 +1,6 @@ -import { ApolloServer, gql } from 'apollo-server'; -import { buildSubgraphSchema } from '@apollo/subgraph'; +import { productsServer } from './server'; -const typeDefs = gql` - extend type Query { - topProducts(first: Int = 5): [Product] - } - - type Product @key(fields: "upc") { - upc: String! - name: String - price: Int - weight: Int - } -`; - -const resolvers = { - Product: { - __resolveReference(object) { - return { - ...object, - ...products.find(product => product.upc === object.upc), - }; - }, - }, - Query: { - topProducts(_, args) { - return products.slice(0, args.first); - }, - }, -}; - -const server = new ApolloServer({ - schema: buildSubgraphSchema([ - { - typeDefs, - resolvers, - }, - ]), +productsServer().catch(error => { + console.error(error); + process.exit(1); }); - -export const productsServer = () => - server.listen({ port: 9873 }).then(({ url }) => { - if (!process.env.CI) { - console.log(`🚀 Server ready at ${url}`); - } - return server; - }); - -const products = [ - { - upc: '1', - name: 'Table', - price: 899, - weight: 100, - }, - { - upc: '2', - name: 'Couch', - price: 1299, - weight: 1000, - }, - { - upc: '3', - name: 'Chair', - price: 54, - weight: 50, - }, -]; diff --git a/examples/federation-example/services/products/server.ts b/examples/federation-example/services/products/server.ts new file mode 100755 index 0000000000000..362f54564bdb8 --- /dev/null +++ b/examples/federation-example/services/products/server.ts @@ -0,0 +1,69 @@ +import { ApolloServer, gql } from 'apollo-server'; +import { buildSubgraphSchema } from '@apollo/subgraph'; + +const typeDefs = gql` + extend type Query { + topProducts(first: Int = 5): [Product] + } + + type Product @key(fields: "upc") { + upc: String! + name: String + price: Int + weight: Int + } +`; + +const resolvers = { + Product: { + __resolveReference(object) { + return { + ...object, + ...products.find(product => product.upc === object.upc), + }; + }, + }, + Query: { + topProducts(_, args) { + return products.slice(0, args.first); + }, + }, +}; + +const server = new ApolloServer({ + schema: buildSubgraphSchema([ + { + typeDefs, + resolvers, + }, + ]), +}); + +export const productsServer = () => + server.listen({ port: 9873 }).then(({ url }) => { + if (!process.env.CI) { + console.log(`🚀 Server ready at ${url}`); + } + return server; + }); + +const products = [ + { + upc: '1', + name: 'Table', + price: 899, + weight: 100, + }, + { + upc: '2', + name: 'Couch', + price: 1299, + weight: 1000, + }, + { + upc: '3', + name: 'Chair', + price: 54, + weight: 50, + }, +]; diff --git a/examples/federation-example/services/reviews/index.ts b/examples/federation-example/services/reviews/index.ts old mode 100755 new mode 100644 index ef2bdab422b4c..8af21f1e02047 --- a/examples/federation-example/services/reviews/index.ts +++ b/examples/federation-example/services/reviews/index.ts @@ -1,95 +1,6 @@ -import { ApolloServer, gql } from 'apollo-server'; -import { buildSubgraphSchema } from '@apollo/subgraph'; +import { reviewsServer } from './server'; -const typeDefs = gql` - type Review @key(fields: "id") { - id: ID! - body: String - author: User @provides(fields: "username") - product: Product - } - - extend type User @key(fields: "id") { - id: ID! @external - username: String @external - reviews: [Review] - } - - extend type Product @key(fields: "upc") { - upc: String! @external - reviews: [Review] - } -`; - -const resolvers = { - Review: { - author(review) { - return { __typename: 'User', id: review.authorID }; - }, - }, - User: { - reviews(user) { - return reviews.filter(review => review.authorID === user.id); - }, - numberOfReviews(user) { - return reviews.filter(review => review.authorID === user.id).length; - }, - username(user) { - const found = usernames.find(username => username.id === user.id); - return found ? found.username : null; - }, - }, - Product: { - reviews(product) { - return reviews.filter(review => review.product.upc === product.upc); - }, - }, -}; - -const server = new ApolloServer({ - schema: buildSubgraphSchema([ - { - typeDefs, - resolvers, - }, - ]), +reviewsServer().catch(error => { + console.error(error); + process.exit(1); }); - -export const reviewsServer = () => - server.listen({ port: 9874 }).then(({ url }) => { - if (!process.env.CI) { - console.log(`🚀 Server ready at ${url}`); - } - return server; - }); - -const usernames = [ - { id: '1', username: '@ada' }, - { id: '2', username: '@complete' }, -]; -const reviews = [ - { - id: '1', - authorID: '1', - product: { upc: '1' }, - body: 'Love it!', - }, - { - id: '2', - authorID: '1', - product: { upc: '2' }, - body: 'Too expensive.', - }, - { - id: '3', - authorID: '2', - product: { upc: '3' }, - body: 'Could be better.', - }, - { - id: '4', - authorID: '2', - product: { upc: '1' }, - body: 'Prefer something else.', - }, -]; diff --git a/examples/federation-example/services/reviews/server.ts b/examples/federation-example/services/reviews/server.ts new file mode 100755 index 0000000000000..ef2bdab422b4c --- /dev/null +++ b/examples/federation-example/services/reviews/server.ts @@ -0,0 +1,95 @@ +import { ApolloServer, gql } from 'apollo-server'; +import { buildSubgraphSchema } from '@apollo/subgraph'; + +const typeDefs = gql` + type Review @key(fields: "id") { + id: ID! + body: String + author: User @provides(fields: "username") + product: Product + } + + extend type User @key(fields: "id") { + id: ID! @external + username: String @external + reviews: [Review] + } + + extend type Product @key(fields: "upc") { + upc: String! @external + reviews: [Review] + } +`; + +const resolvers = { + Review: { + author(review) { + return { __typename: 'User', id: review.authorID }; + }, + }, + User: { + reviews(user) { + return reviews.filter(review => review.authorID === user.id); + }, + numberOfReviews(user) { + return reviews.filter(review => review.authorID === user.id).length; + }, + username(user) { + const found = usernames.find(username => username.id === user.id); + return found ? found.username : null; + }, + }, + Product: { + reviews(product) { + return reviews.filter(review => review.product.upc === product.upc); + }, + }, +}; + +const server = new ApolloServer({ + schema: buildSubgraphSchema([ + { + typeDefs, + resolvers, + }, + ]), +}); + +export const reviewsServer = () => + server.listen({ port: 9874 }).then(({ url }) => { + if (!process.env.CI) { + console.log(`🚀 Server ready at ${url}`); + } + return server; + }); + +const usernames = [ + { id: '1', username: '@ada' }, + { id: '2', username: '@complete' }, +]; +const reviews = [ + { + id: '1', + authorID: '1', + product: { upc: '1' }, + body: 'Love it!', + }, + { + id: '2', + authorID: '1', + product: { upc: '2' }, + body: 'Too expensive.', + }, + { + id: '3', + authorID: '2', + product: { upc: '3' }, + body: 'Could be better.', + }, + { + id: '4', + authorID: '2', + product: { upc: '1' }, + body: 'Prefer something else.', + }, +]; diff --git a/examples/federation-example/tests/federation-example.test.ts b/examples/federation-example/tests/federation-example.test.ts index 54b63ae40f61d..fb9982597f63a 100644 --- a/examples/federation-example/tests/federation-example.test.ts +++ b/examples/federation-example/tests/federation-example.test.ts @@ -1,13 +1,11 @@ -import { findAndParseConfig } from '@graphql-mesh/cli'; -import { MeshInstance, getMesh } from '@graphql-mesh/runtime'; -import { join } from 'path'; -import { accountsServer } from '../services/accounts'; -import { inventoryServer } from '../services/inventory'; -import { productsServer } from '../services/products'; -import { reviewsServer } from '../services/reviews'; -import { ApolloServer } from 'apollo-server'; - import { mkdirSync, readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { findAndParseConfig } from '@graphql-mesh/cli'; +import { getMesh, MeshInstance } from '@graphql-mesh/runtime'; +import { accountsServer } from '../services/accounts/server'; +import { inventoryServer } from '../services/inventory/server'; +import { productsServer } from '../services/products/server'; +import { reviewsServer } from '../services/reviews/server'; const problematicModulePath = join(__dirname, '../../../node_modules/core-js/features/array'); const emptyModuleContent = 'module.exports = {};'; @@ -20,8 +18,10 @@ writeFileSync(join(problematicModulePath, './flat.js'), emptyModuleContent); writeFileSync(join(problematicModulePath, './flat-map.js'), emptyModuleContent); describe('Federation Example', () => { - let mesh; - let servicesToStop: Array = []; + let mesh: MeshInstance; + let servicesToStop: Array<{ + stop: () => Promise; + }> = []; beforeAll(async () => { const [config, ...services] = await Promise.all([ findAndParseConfig({ diff --git a/packages/runtime/src/useSubschema.ts b/packages/runtime/src/useSubschema.ts index 4001a82b2c0fa..3b35b0a5ead2d 100644 --- a/packages/runtime/src/useSubschema.ts +++ b/packages/runtime/src/useSubschema.ts @@ -28,7 +28,7 @@ function getIntrospectionOperationType( visit(operationAST, { Field: node => { if (node.name.value === '__schema' || node.name.value === '__type') { - introspectionQueryType = true; + introspectionQueryType = IntrospectionQueryType.REGULAR; return BREAK; } if (node.name.value === '_service') { diff --git a/yarn.lock b/yarn.lock index f8da1a6cd4033..00e31c5c2d4f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7269,6 +7269,13 @@ apollo-reporting-protobuf@^3.3.1, apollo-reporting-protobuf@^3.3.3: dependencies: "@apollo/protobufjs" "1.2.6" +apollo-server-caching@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz#f501cbeb820a4201d98c2b768c085f22848d9dc5" + integrity sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ== + dependencies: + lru-cache "^6.0.0" + apollo-server-core@^3.11.1: version "3.11.1" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.11.1.tgz#89d83aeaa71a59f760ebfa35bb0cbd31e15474ca"