diff --git a/examples/apollo-federation/__integration-tests__/apollo-federation.spec.ts b/examples/apollo-federation/__integration-tests__/apollo-federation.spec.ts index 68673a843d..b2da95cab9 100644 --- a/examples/apollo-federation/__integration-tests__/apollo-federation.spec.ts +++ b/examples/apollo-federation/__integration-tests__/apollo-federation.spec.ts @@ -1,9 +1,8 @@ -import { gateway, DataSource } from '../gateway/gateway' -import { yoga as service1 } from '../service/yoga' +import { buildService, gateway } from '../gateway/src/gateway' +import { yoga as service1 } from '../service/src/yoga' import { createServer, Server } from 'http' import { AddressInfo } from 'net' -import { fetch } from '@whatwg-node/fetch' -import type { GatewayConfig } from '@apollo/gateway' +import { fetch, File, FormData } from '@whatwg-node/fetch' describe('apollo-federation example integration', () => { let gatewayServer: Server @@ -16,19 +15,12 @@ describe('apollo-federation example integration', () => { await new Promise((resolve) => serviceServer.listen(0, resolve)) servicePort = (serviceServer.address() as AddressInfo).port - const gatewayConfig: GatewayConfig = { + const gatewayService = await gateway({ serviceList: [ { name: 'accounts', url: `http://localhost:${servicePort}/graphql` }, ], - introspectionHeaders: { - accept: 'application/json', - }, - buildService({ url }) { - return new DataSource({ url }) - }, - } - - const gatewayService = await gateway(gatewayConfig) + buildService, + }) gatewayServer = createServer(gatewayService) await new Promise((resolve) => gatewayServer.listen(0, resolve)) gatewayPort = (gatewayServer.address() as AddressInfo).port @@ -51,4 +43,37 @@ describe('apollo-federation example integration', () => { }, }) }) + it('should forward file uploads', async () => { + const formData = new FormData() + formData.append( + 'operations', + JSON.stringify({ + query: 'query($file: File!){readTextFile(file: $file)}', + variables: { + file: null, + }, + }), + ) + formData.append( + 'map', + JSON.stringify({ + 0: ['variables.file'], + }), + ) + formData.append( + '0', + new File(['test'], 'test.txt', { + type: 'text/plain', + }), + ) + const response = await fetch(`http://localhost:${gatewayPort}/graphql`, { + method: 'POST', + body: formData, + }) + const body = await response.json() + expect(body.errors).toBeUndefined() + expect(body.data).toEqual({ + readTextFile: 'test', + }) + }) }) diff --git a/examples/apollo-federation/gateway/gateway.js b/examples/apollo-federation/gateway/gateway.js deleted file mode 100644 index f7b82c2270..0000000000 --- a/examples/apollo-federation/gateway/gateway.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable */ -const { createYoga } = require('graphql-yoga') -const { ApolloGateway, RemoteGraphQLDataSource } = require('@apollo/gateway') -const { useApolloFederation } = require('@envelop/apollo-federation') - -export async function gateway(config) { - // Initialize the gateway - const gateway = new ApolloGateway(config) - - // Make sure all services are loaded - await gateway.load() - - const yoga = createYoga({ - plugins: [ - useApolloFederation({ - gateway, - }), - ], - }) - - return yoga -} - -/** - * Needed since federation remote data source fetcher - * doesn't support `application/graphql-response+json` content type - * By default Yoga uses `application/graphql-response+json` content type as per the GraphQL over HTTP spec - * https://github.com/apollographql/federation/issues/2161 - */ -export class DataSource extends RemoteGraphQLDataSource { - willSendRequest({ request }) { - request.http.headers.set('accept', 'application/json') - } -} diff --git a/examples/apollo-federation/gateway/package.json b/examples/apollo-federation/gateway/package.json index 39172cd86f..0e4b1f2db6 100644 --- a/examples/apollo-federation/gateway/package.json +++ b/examples/apollo-federation/gateway/package.json @@ -3,12 +3,13 @@ "version": "1.3.1", "private": true, "scripts": { - "start": "node index.js", + "start": "ts-node src/index.ts", "check": "exit 0" }, "dependencies": { "@apollo/gateway": "^2.0.0", "@envelop/apollo-federation": "3.0.4", + "@graphql-tools/executor-http": "^0.1.1", "graphql-yoga": "3.3.1", "graphql": "^16.5.0" } diff --git a/examples/apollo-federation/gateway/src/gateway.ts b/examples/apollo-federation/gateway/src/gateway.ts new file mode 100644 index 0000000000..0c929e5d47 --- /dev/null +++ b/examples/apollo-federation/gateway/src/gateway.ts @@ -0,0 +1,56 @@ +import { + ApolloGateway, + GraphQLDataSource, + ServiceEndpointDefinition, +} from '@apollo/gateway' +import { createYoga, isAsyncIterable } from 'graphql-yoga' +import { useApolloFederation } from '@envelop/apollo-federation' +import { GatewayConfig } from '@apollo/gateway' +import { buildHTTPExecutor } from '@graphql-tools/executor-http' +import { parse } from 'graphql' + +export function buildService( + opts: ServiceEndpointDefinition, +): GraphQLDataSource { + const executor = buildHTTPExecutor({ + endpoint: opts.url, + }) + return { + async process(opts) { + const result = await executor({ + document: parse(opts.request.query), + operationName: opts.request.operationName, + variables: opts.request.variables, + context: opts.context, + extensions: { + endpoint: opts.request.http?.url, + method: opts.request.http?.method as 'POST', + headers: opts.request.http?.headers as any, + ...opts.request.extensions, + }, + }) + if (isAsyncIterable(result)) { + throw new Error('Async Iterables are not supported') + } + return result + }, + } +} + +export async function gateway(config?: GatewayConfig) { + // Initialize the gateway + const gateway = new ApolloGateway(config) + + // Make sure all services are loaded + await gateway.load() + + const yoga = createYoga({ + plugins: [ + useApolloFederation({ + gateway, + }), + ], + }) + + return yoga +} diff --git a/examples/apollo-federation/gateway/index.js b/examples/apollo-federation/gateway/src/index.ts similarity index 67% rename from examples/apollo-federation/gateway/index.js rename to examples/apollo-federation/gateway/src/index.ts index 2e5577433f..d83421566a 100644 --- a/examples/apollo-federation/gateway/index.js +++ b/examples/apollo-federation/gateway/src/index.ts @@ -1,16 +1,13 @@ -/* eslint-disable */ -const { createServer } = require('http') -const { gateway, DataSource } = require('./gateway') +import { createServer } from 'http' +import { buildService, gateway } from './gateway' async function main() { - const yoga = gateway({ + const yoga = await gateway({ serviceList: [ { name: 'accounts', url: 'http://localhost:4001/graphql' }, // ...additional subgraphs... ], - buildService({ url }) { - return new DataSource({ url }) - }, + buildService, }) // Start the server and explore http://localhost:4000/graphql diff --git a/examples/apollo-federation/package.json b/examples/apollo-federation/package.json index fe2d1af8c2..6ee8bf73a9 100644 --- a/examples/apollo-federation/package.json +++ b/examples/apollo-federation/package.json @@ -9,7 +9,9 @@ "check": "exit 0" }, "dependencies": { - "concurrently": "^7.0.0" + "concurrently": "^7.0.0", + "ts-node": "^10.9.1", + "typescript": "^4.9.4" }, "workspaces": [ "service", diff --git a/examples/apollo-federation/service/package.json b/examples/apollo-federation/service/package.json index a70de03e5b..b7cab3fa63 100644 --- a/examples/apollo-federation/service/package.json +++ b/examples/apollo-federation/service/package.json @@ -3,7 +3,7 @@ "version": "1.3.1", "private": true, "scripts": { - "start": "node index.js", + "start": "ts-node src/index.ts", "check": "exit 0" }, "dependencies": { diff --git a/examples/apollo-federation/service/index.js b/examples/apollo-federation/service/src/index.ts similarity index 56% rename from examples/apollo-federation/service/index.js rename to examples/apollo-federation/service/src/index.ts index 87dfaa00d3..c0f1fe6509 100644 --- a/examples/apollo-federation/service/index.js +++ b/examples/apollo-federation/service/src/index.ts @@ -1,6 +1,5 @@ -/* eslint-disable */ -const { createServer } = require('http') -const { yoga } = require('./yoga') +import { createServer } from 'http' +import { yoga } from './yoga' const server = createServer(yoga) diff --git a/examples/apollo-federation/service/yoga.js b/examples/apollo-federation/service/src/yoga.ts similarity index 64% rename from examples/apollo-federation/service/yoga.js rename to examples/apollo-federation/service/src/yoga.ts index 349cfc2db4..1360ededc9 100644 --- a/examples/apollo-federation/service/yoga.js +++ b/examples/apollo-federation/service/src/yoga.ts @@ -1,11 +1,13 @@ -/* eslint-disable */ -const { parse } = require('graphql') -const { buildSubgraphSchema } = require('@apollo/subgraph') -const { createYoga } = require('graphql-yoga') +import { parse } from 'graphql' +import { buildSubgraphSchema } from '@apollo/subgraph' +import { createYoga } from 'graphql-yoga' const typeDefs = parse(/* GraphQL */ ` + scalar File + type Query { me: User + readTextFile(file: File!): String } type User @key(fields: "id") { @@ -19,6 +21,9 @@ const resolvers = { me() { return { id: '1', username: '@ava' } }, + readTextFile(_, { file }) { + return file.text() + }, }, User: { __resolveReference(user, { fetchUserById }) { diff --git a/examples/apollo-federation/tsconfig.json b/examples/apollo-federation/tsconfig.json new file mode 100644 index 0000000000..b07b9711b2 --- /dev/null +++ b/examples/apollo-federation/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "target": "esnext", + "moduleResolution": "node", + "module": "commonjs", + "sourceMap": true, + "lib": ["esnext"] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c521582df..1b09517a1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,18 +128,24 @@ importers: examples/apollo-federation: specifiers: concurrently: ^7.0.0 + ts-node: ^10.9.1 + typescript: ^4.9.4 dependencies: concurrently: 7.4.0 + ts-node: 10.9.1_typescript@4.9.4 + typescript: 4.9.4 examples/apollo-federation/gateway: specifiers: '@apollo/gateway': ^2.0.0 '@envelop/apollo-federation': 3.0.4 + '@graphql-tools/executor-http': ^0.1.1 graphql: 16.6.0 graphql-yoga: 3.3.1 dependencies: '@apollo/gateway': 2.1.4_graphql@16.6.0 '@envelop/apollo-federation': 3.0.4_f64zt4rcl7bxo3ftdbmruziebq + '@graphql-tools/executor-http': 0.1.1_graphql@16.6.0 graphql: 16.6.0 graphql-yoga: link:../../../packages/graphql-yoga