From bacdeae80de05bc570343b348552a8a209b647b8 Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Thu, 15 Nov 2018 08:35:52 +0100 Subject: [PATCH 01/11] feat(fastify) Apollo Fastify server integration resolve #626 --- CHANGELOG.md | 1 + README.md | 23 + package.json | 2 + packages/apollo-server-fastify/.npmignore | 6 + packages/apollo-server-fastify/README.md | 45 + packages/apollo-server-fastify/jest.config.js | 3 + packages/apollo-server-fastify/package.json | 42 + .../apollo-server-fastify/src/ApolloServer.ts | 145 +++ .../src/__tests__/ApolloServer.test.ts | 835 ++++++++++++++++++ .../src/__tests__/datasource.test.ts | 143 +++ .../src/__tests__/fastifyApollo.test.ts | 38 + .../src/__tests__/tsconfig.json | 8 + .../src/fastifyApollo.ts | 77 ++ packages/apollo-server-fastify/src/index.ts | 29 + packages/apollo-server-fastify/tsconfig.json | 12 + tsconfig.build.json | 1 + tsconfig.test.json | 1 + 17 files changed, 1411 insertions(+) create mode 100644 packages/apollo-server-fastify/.npmignore create mode 100644 packages/apollo-server-fastify/README.md create mode 100644 packages/apollo-server-fastify/jest.config.js create mode 100644 packages/apollo-server-fastify/package.json create mode 100644 packages/apollo-server-fastify/src/ApolloServer.ts create mode 100644 packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts create mode 100644 packages/apollo-server-fastify/src/__tests__/datasource.test.ts create mode 100644 packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts create mode 100644 packages/apollo-server-fastify/src/__tests__/tsconfig.json create mode 100644 packages/apollo-server-fastify/src/fastifyApollo.ts create mode 100644 packages/apollo-server-fastify/src/index.ts create mode 100644 packages/apollo-server-fastify/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f58fce271a9..73f9a9ee656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ client reference ID, Apollo Server will now default to the values present in the of the request (`apollographql-client-name`, `apollographql-client-reference-id` and `apollographql-client-version` respectively). As a last resort, when those headers are not set, the query extensions' `clientInfo` values will be used. [PR #1960](https://github.com/apollographql/apollo-server/pull/1960) +- Added `apollo-server-fastify` integration ([@rkorrelboom](https://github.com/rkorrelboom) in [#1971](https://github.com/apollostack/apollo-server/pull/1971)) ### v2.2.2 diff --git a/README.md b/README.md index a5c413a7b95..955eaf46fc8 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,29 @@ new ApolloServer({ }) ``` +## Fastify + +```js +const { ApolloServer, gql } = require('apollo-server-fastify'); +const fastify = require('fastify'); + +async function StartServer() { + const server = new ApolloServer({ typeDefs, resolvers }); + + const app = fastify(); + + await server.applyMiddleware({ + app, + }); + + await server.installSubscriptionHandlers(app.server); + + await app.listen(3000); +} + +StartServer().catch(error => console.log(error)); +``` + ### AWS Lambda Apollo Server can be run on Lambda and deployed with AWS Serverless Application Model (SAM). It requires an API Gateway with Lambda Proxy Integration. diff --git a/package.json b/package.json index e9ad092f411..566d1a541a8 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "apollo-server-env": "file:packages/apollo-server-env", "apollo-server-errors": "file:packages/apollo-server-errors", "apollo-server-express": "file:packages/apollo-server-express", + "apollo-server-fastify": "file:packages/apollo-server-fastify", "apollo-server-hapi": "file:packages/apollo-server-hapi", "apollo-server-integration-testsuite": "file:packages/apollo-server-integration-testsuite", "apollo-server-koa": "file:packages/apollo-server-koa", @@ -94,6 +95,7 @@ "codecov": "3.1.0", "connect": "3.6.6", "express": "4.16.4", + "fastify": "1.13.0", "fibers": "3.1.1", "form-data": "2.3.3", "graphql": "14.0.2", diff --git a/packages/apollo-server-fastify/.npmignore b/packages/apollo-server-fastify/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-server-fastify/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-server-fastify/README.md b/packages/apollo-server-fastify/README.md new file mode 100644 index 00000000000..ec61b495c58 --- /dev/null +++ b/packages/apollo-server-fastify/README.md @@ -0,0 +1,45 @@ +--- +title: Fastify +description: Setting up Apollo Server with Fastify +--- + +[![npm version](https://badge.fury.io/js/apollo-server-fastify.svg)](https://badge.fury.io/js/apollo-server-fastify) [![Build Status](https://circleci.com/gh/apollographql/apollo-server.svg?style=svg)](https://circleci.com/gh/apollographql/apollo-server) [![Coverage Status](https://coveralls.io/repos/github/apollographql/apollo-server/badge.svg?branch=master)](https://coveralls.io/github/apollographql/apollo-server?branch=master) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://www.apollographql.com/#slack) + +This is the Fastify integration of GraphQL Server. Apollo Server is a community-maintained open-source GraphQL server that works with many Node.js HTTP server frameworks. [Read the docs](https://www.apollographql.com/docs/apollo-server/). [Read the CHANGELOG.](https://github.com/apollographql/apollo-server/blob/master/CHANGELOG.md) + +```sh +npm install apollo-server-fastify +``` + +## Fastify + +```js +const { ApolloServer, gql } = require('apollo-server-fastify'); +const fastify = require('fastify'); + +async function StartServer() { + const server = new ApolloServer({ typeDefs, resolvers }); + + const app = fastify(); + + await server.applyMiddleware({ + app, + }); + + await server.installSubscriptionHandlers(app.server); + + await app.listen(3000); +} + +StartServer().catch(error => console.log(error)); +``` + +## Principles + +GraphQL Server is built with the following principles in mind: + +* **By the community, for the community**: GraphQL Server's development is driven by the needs of developers +* **Simplicity**: by keeping things simple, GraphQL Server is easier to use, easier to contribute to, and more secure +* **Performance**: GraphQL Server is well-tested and production-ready - no modifications needed + +Anyone is welcome to contribute to GraphQL Server, just read [CONTRIBUTING.md](https://github.com/apollographql/apollo-server/blob/master/CONTRIBUTING.md), take a look at the [roadmap](https://github.com/apollographql/apollo-server/blob/master/ROADMAP.md) and make your first PR! diff --git a/packages/apollo-server-fastify/jest.config.js b/packages/apollo-server-fastify/jest.config.js new file mode 100644 index 00000000000..a383fbc925f --- /dev/null +++ b/packages/apollo-server-fastify/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../jest.config.base'); + +module.exports = Object.assign(Object.create(null), config); diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json new file mode 100644 index 00000000000..dd7abeb687b --- /dev/null +++ b/packages/apollo-server-fastify/package.json @@ -0,0 +1,42 @@ +{ + "name": "apollo-server-fastify", + "version": "2.2.2", + "description": "Production-ready Node.js GraphQL server for Fastify", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify" + }, + "keywords": [ + "GraphQL", + "Apollo", + "Server", + "Fastify", + "Javascript" + ], + "author": "opensource@apollographql.com", + "license": "MIT", + "bugs": { + "url": "https://github.com/apollographql/apollo-server/issues" + }, + "homepage": "https://github.com/apollographql/apollo-server#readme", + "engines": { + "node": ">=6" + }, + "dependencies": { + "@apollographql/apollo-upload-server": "^5.0.3", + "@apollographql/graphql-playground-html": "^1.6.4", + "apollo-server-core": "file:../apollo-server-core", + "fastify-accepts": "^0.5.0", + "fastify-cors": "^0.2.0", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0" + }, + "devDependencies": { + "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" + }, + "peerDependencies": { + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + } +} diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts new file mode 100644 index 00000000000..3a9fa3650c1 --- /dev/null +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -0,0 +1,145 @@ +import { renderPlaygroundPage } from '@apollographql/graphql-playground-html'; +import { Accepts } from 'accepts'; +import { + ApolloServerBase, + PlaygroundRenderPageOptions, +} from 'apollo-server-core'; +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { IncomingMessage, OutgoingMessage } from 'http'; +import { processRequest as processFileUploads } from '@apollographql/apollo-upload-server'; +import { graphqlFastify } from './fastifyApollo'; + +const fastJson = require('fast-json-stringify'); + +export interface ServerRegistration { + app: FastifyInstance; + path?: string; + cors?: object | boolean; + onHealthCheck?: (req: FastifyRequest) => Promise; + disableHealthCheck?: boolean; +} + +const stringifyHealthCheck = fastJson({ + type: 'object', + properties: { + status: { + type: 'string', + }, + }, +}); + +export class ApolloServer extends ApolloServerBase { + protected supportsSubscriptions(): boolean { + return true; + } + + protected supportsUploads(): boolean { + return true; + } + + public async applyMiddleware({ + app, + path, + cors, + disableHealthCheck, + onHealthCheck, + }: ServerRegistration) { + await this.willStart(); + + if (!path) path = '/graphql'; + + this.graphqlPath = path; + + app.register(require('fastify-accepts')); + + if (!disableHealthCheck) { + app.get('/.well-known/apollo/server-health', async (req, res) => { + // Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01 + res.type('application/health+json'); + + if (onHealthCheck) { + try { + await onHealthCheck(req); + res.send(stringifyHealthCheck({ status: 'pass' })); + } catch (e) { + res.status(503).send(stringifyHealthCheck({ status: 'fail' })); + } + } else { + res.send(stringifyHealthCheck({ status: 'pass' })); + } + }); + } + + if (cors === true) { + app.register(require('fastify-cors')); + } else if (cors !== false) { + app.register(require('fastify-cors'), cors); + } + + app.register( + async instance => { + instance.setNotFoundHandler((_request, reply) => { + reply.code(405); + reply.header('allow', 'GET, POST'); + reply.send(); + }); + + instance.addContentTypeParser( + 'multipart', + async (request: IncomingMessage) => + processFileUploads(request, this.uploadsConfig), + ); + + instance.register(graphqlFastify, { + route: { + beforeHandler: ( + req: FastifyRequest, + reply: FastifyReply, + done: () => void, + ) => { + // Note: if you enable playground in production and expect to be able to see your + // schema, you'll need to manually specify `introspection: true` in the + // ApolloServer constructor; by default, the introspection query is only + // enabled in dev. + if (this.playgroundOptions && req.req.method === 'GET') { + // perform more expensive content-type check only if necessary + const accept = (req as any).accepts() as Accepts; + const types = accept.types() as string[]; + const prefersHTML = + types.find( + (x: string) => + x === 'text/html' || x === 'application/json', + ) === 'text/html'; + + if (prefersHTML) { + const playgroundRenderPageOptions: PlaygroundRenderPageOptions = { + endpoint: path, + subscriptionEndpoint: this.subscriptionsPath, + ...this.playgroundOptions, + }; + reply.type('text/html'); + const playground = renderPlaygroundPage( + playgroundRenderPageOptions, + ); + reply.send(playground); + return; + } + } + done(); + }, + }, + graphqlOptions: this.graphQLServerOptions.bind(this), + }); + }, + { + prefix: path, + }, + ); + } +} + +export const registerServer = () => { + throw new Error( + 'Please use server.applyMiddleware instead of registerServer. This warning will be removed in the next release', + ); +}; diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts new file mode 100644 index 00000000000..069405f1a45 --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -0,0 +1,835 @@ +import { FastifyInstance } from 'fastify'; +import fastify from 'fastify'; + +import http from 'http'; + +import request from 'request'; +import FormData from 'form-data'; +import fs from 'fs'; +import { createApolloFetch } from 'apollo-fetch'; + +import { gql, AuthenticationError, Config } from 'apollo-server-core'; +import { ApolloServer, ServerRegistration } from '../ApolloServer'; + +import { + atLeastMajorNodeVersion, + testApolloServer, + createServerInfo, +} from 'apollo-server-integration-testsuite'; + +const typeDefs = gql` + type Query { + hello: String + } +`; + +const resolvers = { + Query: { + hello: () => 'hi', + }, +}; + +const port = 8888; + +describe('apollo-server-fastify', () => { + let server: ApolloServer; + let httpServer: http.Server; + let app: FastifyInstance; + + testApolloServer( + async options => { + server = new ApolloServer(options); + app = fastify(); + await server.applyMiddleware({ app }); + await app.listen(port); + return createServerInfo(server, app.server); + }, + async () => { + if (server) await server.stop(); + if (app) await new Promise(resolve => app.close(() => resolve())); + if (httpServer && httpServer.listening) await httpServer.close(); + }, + ); +}); + +describe('apollo-server-fastify', () => { + let server: ApolloServer; + let app: FastifyInstance; + let httpServer: http.Server; + + async function createServer( + serverOptions: Config, + options: Partial = {}, + ) { + server = new ApolloServer(serverOptions); + app = fastify(); + + await server.applyMiddleware({ ...options, app }); + await app.listen(port); + + return createServerInfo(server, app.server); + } + + afterEach(async () => { + if (server) await server.stop(); + if (app) await new Promise(resolve => app.close(() => resolve())); + if (httpServer) await httpServer.close(); + }); + + describe('constructor', async () => { + it('accepts typeDefs and resolvers', () => { + return createServer({ typeDefs, resolvers }); + }); + }); + + describe('applyMiddleware', async () => { + it('can be queried', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + }); + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: '{hello}' }); + + expect(result.data).toEqual({ hello: 'hi' }); + expect(result.errors).toBeUndefined(); + }); + + // XXX Unclear why this would be something somebody would want (vs enabling + // introspection without graphql-playground, which seems reasonable, eg you + // have your own graphql-playground setup with a custom link) + it('can enable playground separately from introspection during production', async () => { + const INTROSPECTION_QUERY = ` + { + __schema { + directives { + name + } + } + } +`; + + const { url: uri } = await createServer({ + typeDefs, + resolvers, + introspection: false, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: INTROSPECTION_QUERY }); + + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual( + 'GRAPHQL_VALIDATION_FAILED', + ); + + return new Promise((resolve, reject) => { + request( + { + url: uri, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + }, + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toMatch('GraphQLPlayground'); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }); + + it('renders GraphQL playground by default when browser requests', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url } = await createServer({ + typeDefs, + resolvers, + }); + + return new Promise((resolve, reject) => { + request( + { + url, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + }, + }, + (error, response, body) => { + process.env.NODE_ENV = nodeEnv; + if (error) { + reject(error); + } else { + expect(body).toMatch('GraphQLPlayground'); + expect(body).not.toMatch('settings'); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }); + + const playgroundPartialOptionsTest = async () => { + const defaultQuery = 'query { foo { bar } }'; + const endpoint = '/fumanchupacabra'; + const { url } = await createServer( + { + typeDefs, + resolvers, + playground: { + // https://github.com/apollographql/graphql-playground/blob/0e452d2005fcd26f10fbdcc4eed3b2e2af935e3a/packages/graphql-playground-html/src/render-playground-page.ts#L16-L24 + // must be made partial + settings: { + 'editor.theme': 'light', + } as any, + tabs: [ + { + query: defaultQuery, + }, + { + endpoint, + } as any, + ], + }, + }, + {}, + ); + + return new Promise((resolve, reject) => { + request( + { + url, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + Folo: 'bar', + }, + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toMatch('GraphQLPlayground'); + expect(body).toMatch(`"editor.theme": "light"`); + expect(body).toMatch(defaultQuery); + expect(body).toMatch(endpoint); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }; + + it('accepts partial GraphQL Playground Options in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + await playgroundPartialOptionsTest(); + process.env.NODE_ENV = nodeEnv; + }); + + it( + 'accepts partial GraphQL Playground Options when an environment is ' + + 'not specified', + async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + await playgroundPartialOptionsTest(); + process.env.NODE_ENV = nodeEnv; + }, + ); + + it('accepts playground options as a boolean', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url } = await createServer( + { + typeDefs, + resolvers, + playground: false, + }, + {}, + ); + + return new Promise((resolve, reject) => { + request( + { + url, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + }, + }, + (error, response, body) => { + process.env.NODE_ENV = nodeEnv; + if (error) { + reject(error); + } else { + expect(body).not.toMatch('GraphQLPlayground'); + expect(response.statusCode).not.toEqual(200); + resolve(); + } + }, + ); + }); + }); + + it('accepts cors configuration', async () => { + const { url: uri } = await createServer( + { + typeDefs, + resolvers, + }, + { + cors: { origin: 'apollographql.com' }, + }, + ); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect( + response.response.headers.get('access-control-allow-origin'), + ).toEqual('apollographql.com'); + next(); + }, + ); + await apolloFetch({ query: '{hello}' }); + }); + + describe('healthchecks', () => { + afterEach(async () => { + await server.stop(); + }); + + it('creates a healthcheck endpoint', async () => { + const { port } = await createServer({ + typeDefs, + resolvers, + }); + + return new Promise((resolve, reject) => { + request( + { + url: `http://localhost:${port}/.well-known/apollo/server-health`, + method: 'GET', + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toEqual(JSON.stringify({ status: 'pass' })); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }); + + it('provides a callback for the healthcheck', async () => { + const { port } = await createServer( + { + typeDefs, + resolvers, + }, + { + onHealthCheck: async () => { + throw Error("can't connect to DB"); + }, + }, + ); + + return new Promise((resolve, reject) => { + request( + { + url: `http://localhost:${port}/.well-known/apollo/server-health`, + method: 'GET', + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toEqual(JSON.stringify({ status: 'fail' })); + expect(response.statusCode).toEqual(503); + resolve(); + } + }, + ); + }); + }); + + it('can disable the healthCheck', async () => { + const { port } = await createServer( + { + typeDefs, + resolvers, + }, + { + disableHealthCheck: true, + }, + ); + + return new Promise((resolve, reject) => { + request( + { + url: `http://localhost:${port}/.well-known/apollo/server-health`, + method: 'GET', + }, + (error, response) => { + if (error) { + reject(error); + } else { + expect(response.statusCode).toEqual(404); + resolve(); + } + }, + ); + }); + }); + }); + // NODE: Intentionally skip file upload tests on Node.js 10 or higher. + (atLeastMajorNodeVersion(10) ? describe.skip : describe)( + 'file uploads', + () => { + it('enabled uploads', async () => { + const { port } = await createServer({ + typeDefs: gql` + type File { + filename: String! + mimetype: String! + encoding: String! + } + + type Query { + uploads: [File] + } + + type Mutation { + singleUpload(file: Upload!): File! + } + `, + resolvers: { + Query: { + uploads: () => {}, + }, + Mutation: { + singleUpload: async (_, args) => { + expect((await args.file).stream).toBeDefined(); + return args.file; + }, + }, + }, + }); + + const body = new FormData(); + + body.append( + 'operations', + JSON.stringify({ + query: ` + mutation($file: Upload!) { + singleUpload(file: $file) { + filename + encoding + mimetype + } + } + `, + variables: { + file: null, + }, + }), + ); + + body.append('map', JSON.stringify({ 1: ['variables.file'] })); + body.append('1', fs.createReadStream('package.json')); + + try { + const resolved = await fetch(`http://localhost:${port}/graphql`, { + method: 'POST', + body: body as any, + }); + const text = await resolved.text(); + const response = JSON.parse(text); + + expect(response.data.singleUpload).toEqual({ + filename: 'package.json', + encoding: '7bit', + mimetype: 'application/json', + }); + } catch (error) { + // This error began appearing randomly and seems to be a dev dependency bug. + // https://github.com/jaydenseric/apollo-upload-server/blob/18ecdbc7a1f8b69ad51b4affbd986400033303d4/test.js#L39-L42 + if (error.code !== 'EPIPE') throw error; + } + }); + }, + ); + + describe('errors', () => { + it('returns thrown context error as a valid graphql result', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { + Query: { + hello: () => { + throw Error('never get here'); + }, + }, + }; + const { url: uri } = await createServer({ + typeDefs, + resolvers, + context: () => { + throw new AuthenticationError('valid result'); + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: '{hello}' }); + expect(result.errors.length).toEqual(1); + expect(result.data).toBeUndefined(); + + const e = result.errors[0]; + expect(e.message).toMatch('valid result'); + expect(e.extensions).toBeDefined(); + expect(e.extensions.code).toEqual('UNAUTHENTICATED'); + expect(e.extensions.exception.stacktrace).toBeDefined(); + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes in dev mode', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url: uri } = await createServer({ + typeDefs: gql` + type Query { + error: String + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).toBeDefined(); + expect(result.data).toEqual({ error: null }); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).toBeDefined(); + expect(result.errors[0].extensions.exception.stacktrace).toBeDefined(); + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createServer({ + typeDefs: gql` + type Query { + error: String + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).toBeDefined(); + expect(result.data).toEqual({ error: null }); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).toBeUndefined(); + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes with null response in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createServer({ + typeDefs: gql` + type Query { + error: String! + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).toBeNull(); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).toBeUndefined(); + + process.env.NODE_ENV = nodeEnv; + }); + }); + }); + + describe('extensions', () => { + const books = [ + { + title: 'H', + author: 'J', + }, + ]; + + const typeDefs = gql` + type Book { + title: String + author: String + } + + type Cook @cacheControl(maxAge: 200) { + title: String + author: String + } + + type Pook @cacheControl(maxAge: 200) { + title: String + books: [Book] @cacheControl(maxAge: 20, scope: PRIVATE) + } + + type Query { + books: [Book] + cooks: [Cook] + pooks: [Pook] + } + `; + + const resolvers = { + Query: { + books: () => books, + cooks: () => books, + pooks: () => [{ title: 'pook', books }], + }, + }; + + describe('Cache Control Headers', () => { + it('applies cacheControl Headers and strips out extension', async () => { + const { url: uri } = await createServer({ typeDefs, resolvers }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toEqual( + 'max-age=200, public', + ); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ cooks { title author } }`, + }); + expect(result.data).toEqual({ cooks: books }); + expect(result.extensions).toBeUndefined(); + }); + + it('contains no cacheControl Headers and keeps extension with engine proxy', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + cacheControl: true, + }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toBeNull(); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ cooks { title author } }`, + }); + expect(result.data).toEqual({ cooks: books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.cacheControl).toBeDefined(); + }); + + it('contains no cacheControl Headers when uncachable', async () => { + const { url: uri } = await createServer({ typeDefs, resolvers }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toBeNull(); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeUndefined(); + }); + + it('contains private cacheControl Headers when scoped', async () => { + const { url: uri } = await createServer({ typeDefs, resolvers }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toEqual( + 'max-age=20, private', + ); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ pooks { title books { title author } } }`, + }); + expect(result.data).toEqual({ + pooks: [{ title: 'pook', books }], + }); + expect(result.extensions).toBeUndefined(); + }); + + it('runs when cache-control is false', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + cacheControl: false, + }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toBeNull(); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ pooks { title books { title author } } }`, + }); + expect(result.data).toEqual({ + pooks: [{ title: 'pook', books }], + }); + expect(result.extensions).toBeUndefined(); + }); + }); + + describe('Tracing', () => { + const typeDefs = gql` + type Book { + title: String + author: String + } + + type Query { + books: [Book] + } + `; + + const resolvers = { + Query: { + books: () => books, + }, + }; + + it('applies tracing extension', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + tracing: true, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.tracing).toBeDefined(); + }); + + it('applies tracing extension with cache control enabled', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + tracing: true, + cacheControl: true, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.tracing).toBeDefined(); + }); + + xit('applies tracing extension with engine enabled', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + tracing: true, + engine: { + apiKey: 'service:my-app:secret', + maxAttempts: 0, + endpointUrl: 'l', + reportErrorFunction: () => {}, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.tracing).toBeDefined(); + }); + }); + }); +}); diff --git a/packages/apollo-server-fastify/src/__tests__/datasource.test.ts b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts new file mode 100644 index 00000000000..943c43aff69 --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts @@ -0,0 +1,143 @@ +import fastify, { FastifyInstance } from 'fastify'; + +import { RESTDataSource } from 'apollo-datasource-rest'; + +import { createApolloFetch } from 'apollo-fetch'; +import { ApolloServer } from '../ApolloServer'; + +import { createServerInfo } from 'apollo-server-integration-testsuite'; +import { gql } from '../index'; + +const restPort = 4001; + +export class IdAPI extends RESTDataSource { + baseURL = `http://localhost:${restPort}/`; + + async getId(id: string) { + return this.get(`id/${id}`); + } + + async getStringId(id: string) { + return this.get(`str/${id}`); + } +} + +const typeDefs = gql` + type Query { + id: String + stringId: String + } +`; + +const resolvers = { + Query: { + id: async (_source, _args, { dataSources }) => { + return (await dataSources.id.getId('hi')).id; + }, + stringId: async (_source, _args, { dataSources }) => { + return dataSources.id.getStringId('hi'); + }, + }, +}; + +let restCalls = 0; +const restAPI = fastify(); + +restAPI.get('/id/:id', (req, res) => { + const id = req.params.id; + restCalls++; + res.header('Content-Type', 'application/json'); + res.header('Cache-Control', 'max-age=2000, public'); + // res.write(JSON.stringify()); + res.send({ id }); +}); + +restAPI.get('/str/:id', (req, res) => { + const id = req.params.id; + restCalls++; + res.header('Content-Type', 'text/plain'); + res.header('Cache-Control', 'max-age=2000, public'); + // res.write(id); + res.send(id); +}); + +describe('apollo-server-fastify', () => { + let restServer: FastifyInstance; + let app: FastifyInstance; + + beforeAll(async () => { + await restAPI.listen(restPort); + }); + + afterAll(async () => { + await new Promise(resolve => restServer.close(() => resolve())); + }); + + let server: ApolloServer; + + beforeEach(() => { + restCalls = 0; + }); + + afterEach(async () => { + await server.stop(); + // await httpServer.close(); + await new Promise(resolve => app.close(() => resolve())); + }); + + it('uses the cache', async () => { + server = new ApolloServer({ + typeDefs, + resolvers, + dataSources: () => ({ + id: new IdAPI(), + }), + }); + app = fastify(); + + await server.applyMiddleware({ app }); + await app.listen(6667); + const { url: uri } = createServerInfo(server, app.server); + + const apolloFetch = createApolloFetch({ uri }); + const firstResult = await apolloFetch({ query: '{ id }' }); + + expect(firstResult.data).toEqual({ id: 'hi' }); + expect(firstResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + + const secondResult = await apolloFetch({ query: '{ id }' }); + + expect(secondResult.data).toEqual({ id: 'hi' }); + expect(secondResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + }); + + it('can cache a string from the backend', async () => { + server = new ApolloServer({ + typeDefs, + resolvers, + dataSources: () => ({ + id: new IdAPI(), + }), + }); + app = fastify(); + + server.applyMiddleware({ app }); + await app.listen(6668); + const { url: uri } = createServerInfo(server, app.server); + + const apolloFetch = createApolloFetch({ uri }); + const firstResult = await apolloFetch({ query: '{ id: stringId }' }); + + expect(firstResult.data).toEqual({ id: 'hi' }); + expect(firstResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + + const secondResult = await apolloFetch({ query: '{ id: stringId }' }); + + expect(secondResult.data).toEqual({ id: 'hi' }); + expect(secondResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + }); +}); diff --git a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts new file mode 100644 index 00000000000..1d1c0dfbc1e --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts @@ -0,0 +1,38 @@ +import fastify from 'fastify'; +import { Server } from 'http'; +import { ApolloServer } from '../ApolloServer'; +import testSuite, { + schema as Schema, + CreateAppOptions, +} from 'apollo-server-integration-testsuite'; +import { GraphQLOptions, Config } from 'apollo-server-core'; + +async function createApp(options: CreateAppOptions = {}) { + const app = fastify(); + + const server = new ApolloServer( + (options.graphqlOptions as Config) || { schema: Schema }, + ); + await server.applyMiddleware({ app }); + await app.listen(); + return app.server; +} + +async function destroyApp(app: Server) { + if (!app || !app.close) { + return; + } + await new Promise(resolve => app.close(resolve)); +} + +describe('fastifyApollo', () => { + it('throws error if called without schema', function() { + expect(() => new ApolloServer(undefined as GraphQLOptions)).toThrow( + 'ApolloServer requires options.', + ); + }); +}); + +describe('integration:Fastify', () => { + testSuite(createApp, destroyApp); +}); diff --git a/packages/apollo-server-fastify/src/__tests__/tsconfig.json b/packages/apollo-server-fastify/src/__tests__/tsconfig.json new file mode 100644 index 00000000000..86b8a49b265 --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.test.base", + "include": ["**/*"], + "references": [ + { "path": "../../" }, + { "path": "../../../apollo-server-integration-testsuite" } + ] +} diff --git a/packages/apollo-server-fastify/src/fastifyApollo.ts b/packages/apollo-server-fastify/src/fastifyApollo.ts new file mode 100644 index 00000000000..37fc2327521 --- /dev/null +++ b/packages/apollo-server-fastify/src/fastifyApollo.ts @@ -0,0 +1,77 @@ +import { + convertNodeHttpToRequest, + GraphQLOptions, + runHttpQuery, +} from 'apollo-server-core'; +import { + FastifyInstance, + FastifyReply, + FastifyRequest, + RegisterOptions, + RouteOptions, +} from 'fastify'; +import { IncomingMessage, OutgoingMessage, Server } from 'http'; + +export interface FastifyGraphQLOptionsFunction + extends RegisterOptions { + route: Partial>; + graphqlOptions: ( + req?: FastifyRequest, + res?: FastifyReply, + ) => GraphQLOptions | Promise; +} + +export async function graphqlFastify( + fastify: FastifyInstance, + options: FastifyGraphQLOptionsFunction, +): Promise { + if (!options) { + throw new Error('Apollo Server requires options.'); + } + + fastify.route({ + method: ['GET', 'POST'], + url: '/', + handler: async ( + request: FastifyRequest, + reply: FastifyReply, + ) => { + try { + const { graphqlResponse, responseInit } = await runHttpQuery( + [request, reply], + { + method: request.req.method as string, + options: options.graphqlOptions, + query: request.req.method === 'POST' ? request.body : request.query, + request: convertNodeHttpToRequest(request.raw), + }, + ); + + if (responseInit.headers) { + for (const [name, value] of Object.entries( + responseInit.headers, + )) { + reply.header(name, value); + } + } + reply.serializer((payload: string) => payload); + reply.send(graphqlResponse); + } catch (error) { + if ('HttpQueryError' !== error.name) { + throw error; + } + + if (error.headers) { + Object.keys(error.headers).forEach(header => { + reply.header(header, error.headers[header]); + }); + } + + reply.code(error.statusCode); + reply.serializer((payload: string) => payload); + reply.send(error.message); + } + }, + ...options.route, + }); +} diff --git a/packages/apollo-server-fastify/src/index.ts b/packages/apollo-server-fastify/src/index.ts new file mode 100644 index 00000000000..38374b22e5f --- /dev/null +++ b/packages/apollo-server-fastify/src/index.ts @@ -0,0 +1,29 @@ +export { + GraphQLUpload, + GraphQLOptions, + GraphQLExtension, + Config, + gql, + // Errors + ApolloError, + toApolloError, + SyntaxError, + ValidationError, + AuthenticationError, + ForbiddenError, + UserInputError, + // playground + defaultPlaygroundOptions, + PlaygroundConfig, + PlaygroundRenderPageOptions, +} from 'apollo-server-core'; + +export * from 'graphql-tools'; +export * from 'graphql-subscriptions'; + +// ApolloServer integration. +export { + ApolloServer, + registerServer, + ServerRegistration, +} from './ApolloServer'; diff --git a/packages/apollo-server-fastify/tsconfig.json b/packages/apollo-server-fastify/tsconfig.json new file mode 100644 index 00000000000..71b94f32842 --- /dev/null +++ b/packages/apollo-server-fastify/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + }, + "include": ["src/**/*"], + "exclude": ["**/__tests__", "**/__mocks__"], + "references": [ + { "path": "../apollo-server-core" }, + ] +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 24a42b86b9e..cd6cd85dea1 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -19,6 +19,7 @@ { "path": "./packages/apollo-server-core" }, { "path": "./packages/apollo-server-errors" }, { "path": "./packages/apollo-server-express" }, + { "path": "./packages/apollo-server-fastify" }, { "path": "./packages/apollo-server-hapi" }, { "path": "./packages/apollo-server-koa" }, { "path": "./packages/apollo-server-lambda" }, diff --git a/tsconfig.test.json b/tsconfig.test.json index 1d906e03b7a..aa6f00ceeb8 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -16,6 +16,7 @@ { "path": "./packages/apollo-server-cloud-functions/src/__tests__/" }, { "path": "./packages/apollo-server-core/src/__tests__/" }, { "path": "./packages/apollo-server-express/src/__tests__/" }, + { "path": "./packages/apollo-server-fastify/src/__tests__/" }, { "path": "./packages/apollo-server-hapi/src/__tests__/" }, { "path": "./packages/apollo-server-koa/src/__tests__/" }, { "path": "./packages/apollo-server-lambda/src/__tests__/" }, From a1d2b74c31ff70d55155139ef8be1623a3075fd7 Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Sat, 17 Nov 2018 09:23:54 +0100 Subject: [PATCH 02/11] feat(fastify) Use createHandler instead of applyMiddleware #626 --- packages/apollo-server-fastify/README.md | 2 +- .../apollo-server-fastify/src/ApolloServer.ts | 111 +++++++++--------- .../src/__tests__/ApolloServer.test.ts | 4 +- .../src/__tests__/datasource.test.ts | 7 +- .../src/__tests__/fastifyApollo.test.ts | 7 +- .../src/fastifyApollo.ts | 102 +++++++--------- 6 files changed, 107 insertions(+), 126 deletions(-) diff --git a/packages/apollo-server-fastify/README.md b/packages/apollo-server-fastify/README.md index ec61b495c58..96e20270e59 100644 --- a/packages/apollo-server-fastify/README.md +++ b/packages/apollo-server-fastify/README.md @@ -22,7 +22,7 @@ async function StartServer() { const app = fastify(); - await server.applyMiddleware({ + await server.createHandler({ app, }); diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index 3a9fa3650c1..f5d215b18a7 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -5,14 +5,13 @@ import { PlaygroundRenderPageOptions, } from 'apollo-server-core'; import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; -import { IncomingMessage, OutgoingMessage } from 'http'; +import { IncomingMessage, OutgoingMessage, Server } from 'http'; import { processRequest as processFileUploads } from '@apollographql/apollo-upload-server'; import { graphqlFastify } from './fastifyApollo'; const fastJson = require('fast-json-stringify'); export interface ServerRegistration { - app: FastifyInstance; path?: string; cors?: object | boolean; onHealthCheck?: (req: FastifyRequest) => Promise; @@ -37,61 +36,61 @@ export class ApolloServer extends ApolloServerBase { return true; } - public async applyMiddleware({ - app, + public async createHandler({ path, cors, disableHealthCheck, onHealthCheck, - }: ServerRegistration) { + }: ServerRegistration = {}) { + this.graphqlPath = path ? path : '/graphql'; await this.willStart(); - if (!path) path = '/graphql'; - - this.graphqlPath = path; - - app.register(require('fastify-accepts')); - - if (!disableHealthCheck) { - app.get('/.well-known/apollo/server-health', async (req, res) => { - // Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01 - res.type('application/health+json'); - - if (onHealthCheck) { - try { - await onHealthCheck(req); + return async ( + app: FastifyInstance, + ) => { + if (!disableHealthCheck) { + app.get('/.well-known/apollo/server-health', async (req, res) => { + // Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01 + res.type('application/health+json'); + + if (onHealthCheck) { + try { + await onHealthCheck(req); + res.send(stringifyHealthCheck({ status: 'pass' })); + } catch (e) { + res.status(503).send(stringifyHealthCheck({ status: 'fail' })); + } + } else { res.send(stringifyHealthCheck({ status: 'pass' })); - } catch (e) { - res.status(503).send(stringifyHealthCheck({ status: 'fail' })); } - } else { - res.send(stringifyHealthCheck({ status: 'pass' })); - } - }); - } - - if (cors === true) { - app.register(require('fastify-cors')); - } else if (cors !== false) { - app.register(require('fastify-cors'), cors); - } - - app.register( - async instance => { - instance.setNotFoundHandler((_request, reply) => { - reply.code(405); - reply.header('allow', 'GET, POST'); - reply.send(); }); + } + + app.register( + async instance => { + instance.register(require('fastify-accepts')); - instance.addContentTypeParser( - 'multipart', - async (request: IncomingMessage) => - processFileUploads(request, this.uploadsConfig), - ); + if (cors === true) { + instance.register(require('fastify-cors')); + } else if (cors !== false) { + instance.register(require('fastify-cors'), cors); + } - instance.register(graphqlFastify, { - route: { + instance.setNotFoundHandler((_request, reply) => { + reply.code(405); + reply.header('allow', 'GET, POST'); + reply.send(); + }); + + instance.addContentTypeParser( + 'multipart', + async (request: IncomingMessage) => + processFileUploads(request, this.uploadsConfig), + ); + + instance.route({ + method: ['GET', 'POST'], + url: '/', beforeHandler: ( req: FastifyRequest, reply: FastifyReply, @@ -113,7 +112,7 @@ export class ApolloServer extends ApolloServerBase { if (prefersHTML) { const playgroundRenderPageOptions: PlaygroundRenderPageOptions = { - endpoint: path, + endpoint: this.graphqlPath, subscriptionEndpoint: this.subscriptionsPath, ...this.playgroundOptions, }; @@ -127,19 +126,19 @@ export class ApolloServer extends ApolloServerBase { } done(); }, - }, - graphqlOptions: this.graphQLServerOptions.bind(this), - }); - }, - { - prefix: path, - }, - ); + handler: await graphqlFastify(this.graphQLServerOptions.bind(this)), + }); + }, + { + prefix: this.graphqlPath, + }, + ); + }; } } export const registerServer = () => { throw new Error( - 'Please use server.applyMiddleware instead of registerServer. This warning will be removed in the next release', + 'Please use server.createHandler instead of registerServer. This warning will be removed in the next release', ); }; diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts index 069405f1a45..68ae1e29d2c 100644 --- a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -40,7 +40,7 @@ describe('apollo-server-fastify', () => { async options => { server = new ApolloServer(options); app = fastify(); - await server.applyMiddleware({ app }); + app.register(await server.createHandler()); await app.listen(port); return createServerInfo(server, app.server); }, @@ -64,7 +64,7 @@ describe('apollo-server-fastify', () => { server = new ApolloServer(serverOptions); app = fastify(); - await server.applyMiddleware({ ...options, app }); + app.register(await server.createHandler(options)); await app.listen(port); return createServerInfo(server, app.server); diff --git a/packages/apollo-server-fastify/src/__tests__/datasource.test.ts b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts index 943c43aff69..79d279f0c5f 100644 --- a/packages/apollo-server-fastify/src/__tests__/datasource.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts @@ -48,7 +48,6 @@ restAPI.get('/id/:id', (req, res) => { restCalls++; res.header('Content-Type', 'application/json'); res.header('Cache-Control', 'max-age=2000, public'); - // res.write(JSON.stringify()); res.send({ id }); }); @@ -57,7 +56,6 @@ restAPI.get('/str/:id', (req, res) => { restCalls++; res.header('Content-Type', 'text/plain'); res.header('Cache-Control', 'max-age=2000, public'); - // res.write(id); res.send(id); }); @@ -81,7 +79,6 @@ describe('apollo-server-fastify', () => { afterEach(async () => { await server.stop(); - // await httpServer.close(); await new Promise(resolve => app.close(() => resolve())); }); @@ -95,7 +92,7 @@ describe('apollo-server-fastify', () => { }); app = fastify(); - await server.applyMiddleware({ app }); + app.register(await server.createHandler()); await app.listen(6667); const { url: uri } = createServerInfo(server, app.server); @@ -123,7 +120,7 @@ describe('apollo-server-fastify', () => { }); app = fastify(); - server.applyMiddleware({ app }); + app.register(await server.createHandler()); await app.listen(6668); const { url: uri } = createServerInfo(server, app.server); diff --git a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts index 1d1c0dfbc1e..65ffa2b9350 100644 --- a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts @@ -13,8 +13,11 @@ async function createApp(options: CreateAppOptions = {}) { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); - await server.applyMiddleware({ app }); - await app.listen(); + + (async function() { + app.register(await server.createHandler()); + await app.listen(); + })(); return app.server; } diff --git a/packages/apollo-server-fastify/src/fastifyApollo.ts b/packages/apollo-server-fastify/src/fastifyApollo.ts index 37fc2327521..ee8945a526c 100644 --- a/packages/apollo-server-fastify/src/fastifyApollo.ts +++ b/packages/apollo-server-fastify/src/fastifyApollo.ts @@ -3,75 +3,57 @@ import { GraphQLOptions, runHttpQuery, } from 'apollo-server-core'; -import { - FastifyInstance, - FastifyReply, - FastifyRequest, - RegisterOptions, - RouteOptions, -} from 'fastify'; -import { IncomingMessage, OutgoingMessage, Server } from 'http'; +import { FastifyReply, FastifyRequest, RequestHandler } from 'fastify'; +import { IncomingMessage, OutgoingMessage } from 'http'; -export interface FastifyGraphQLOptionsFunction - extends RegisterOptions { - route: Partial>; - graphqlOptions: ( +export async function graphqlFastify( + options: ( req?: FastifyRequest, res?: FastifyReply, - ) => GraphQLOptions | Promise; -} - -export async function graphqlFastify( - fastify: FastifyInstance, - options: FastifyGraphQLOptionsFunction, -): Promise { + ) => GraphQLOptions | Promise, +): Promise> { if (!options) { throw new Error('Apollo Server requires options.'); } - fastify.route({ - method: ['GET', 'POST'], - url: '/', - handler: async ( - request: FastifyRequest, - reply: FastifyReply, - ) => { - try { - const { graphqlResponse, responseInit } = await runHttpQuery( - [request, reply], - { - method: request.req.method as string, - options: options.graphqlOptions, - query: request.req.method === 'POST' ? request.body : request.query, - request: convertNodeHttpToRequest(request.raw), - }, - ); + return async ( + request: FastifyRequest, + reply: FastifyReply, + ) => { + try { + const { graphqlResponse, responseInit } = await runHttpQuery( + [request, reply], + { + method: request.req.method as string, + options, + query: request.req.method === 'POST' ? request.body : request.query, + request: convertNodeHttpToRequest(request.raw), + }, + ); - if (responseInit.headers) { - for (const [name, value] of Object.entries( - responseInit.headers, - )) { - reply.header(name, value); - } - } - reply.serializer((payload: string) => payload); - reply.send(graphqlResponse); - } catch (error) { - if ('HttpQueryError' !== error.name) { - throw error; - } - - if (error.headers) { - Object.keys(error.headers).forEach(header => { - reply.header(header, error.headers[header]); - }); + if (responseInit.headers) { + for (const [name, value] of Object.entries( + responseInit.headers, + )) { + reply.header(name, value); } + } + reply.serializer((payload: string) => payload); + reply.send(graphqlResponse); + } catch (error) { + if ('HttpQueryError' !== error.name) { + throw error; + } - reply.code(error.statusCode); - reply.serializer((payload: string) => payload); - reply.send(error.message); + if (error.headers) { + Object.keys(error.headers).forEach(header => { + reply.header(header, error.headers[header]); + }); } - }, - ...options.route, - }); + + reply.code(error.statusCode); + reply.serializer((payload: string) => payload); + reply.send(error.message); + } + }; } From 88ff16192fc01a3e180c91ba8371c5f47878d116 Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Sat, 17 Nov 2018 11:45:04 +0100 Subject: [PATCH 03/11] feat(fastify) Fix integration test for node 10 #626 --- .../src/__tests__/fastifyApollo.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts index 65ffa2b9350..c85c0e44b83 100644 --- a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts @@ -14,10 +14,9 @@ async function createApp(options: CreateAppOptions = {}) { (options.graphqlOptions as Config) || { schema: Schema }, ); - (async function() { - app.register(await server.createHandler()); - await app.listen(); - })(); + app.register(await server.createHandler()); + await app.listen(); + return app.server; } From fb023a5d4c5dcd5e070179b72c83015ccc916e2d Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Sat, 17 Nov 2018 11:50:31 +0100 Subject: [PATCH 04/11] feat(fastify) Update README's with fastify createHandler interface #626 --- README.md | 23 ++++++++++------------- packages/apollo-server-fastify/README.md | 22 +++++++++------------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 955eaf46fc8..803e976c9fc 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ Often times, Apollo Server needs to be run with a particular integration. To sta - `apollo-server-express` - `apollo-server-koa` - `apollo-server-hapi` +- `apollo-server-fastify` - `apollo-server-lambda` - `apollo-server-azure-functions` - `apollo-server-cloud-functions` @@ -239,23 +240,19 @@ new ApolloServer({ ```js const { ApolloServer, gql } = require('apollo-server-fastify'); -const fastify = require('fastify'); +const { typeDefs, resolvers } = require('./module'); -async function StartServer() { - const server = new ApolloServer({ typeDefs, resolvers }); - - const app = fastify(); - - await server.applyMiddleware({ - app, - }); +const server = new ApolloServer({ + typeDefs, + resolvers, +}); - await server.installSubscriptionHandlers(app.server); +const app = require('fastify')(); +(async function () { + app.register(await server.createHandler()); await app.listen(3000); -} - -StartServer().catch(error => console.log(error)); +})(); ``` ### AWS Lambda diff --git a/packages/apollo-server-fastify/README.md b/packages/apollo-server-fastify/README.md index 96e20270e59..230569ec2d3 100644 --- a/packages/apollo-server-fastify/README.md +++ b/packages/apollo-server-fastify/README.md @@ -15,23 +15,19 @@ npm install apollo-server-fastify ```js const { ApolloServer, gql } = require('apollo-server-fastify'); -const fastify = require('fastify'); +const { typeDefs, resolvers } = require('./module'); -async function StartServer() { - const server = new ApolloServer({ typeDefs, resolvers }); +const server = new ApolloServer({ + typeDefs, + resolvers, +}); - const app = fastify(); - - await server.createHandler({ - app, - }); - - await server.installSubscriptionHandlers(app.server); +const app = require('fastify')(); +(async function () { + app.register(await server.createHandler()); await app.listen(3000); -} - -StartServer().catch(error => console.log(error)); +})(); ``` ## Principles From 482cdad6fa5f15daecb13bd48b27f3af8d660456 Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Mon, 19 Nov 2018 12:31:27 +0100 Subject: [PATCH 05/11] feat(fastify) Implement the fastify createHandler as a synchronous method #626 --- README.md | 2 +- packages/apollo-server-fastify/README.md | 2 +- packages/apollo-server-fastify/src/ApolloServer.ts | 6 ++++-- .../src/__tests__/ApolloServer.test.ts | 4 ++-- .../apollo-server-fastify/src/__tests__/datasource.test.ts | 4 ++-- .../src/__tests__/fastifyApollo.test.ts | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 803e976c9fc..d2153a300fe 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ const server = new ApolloServer({ const app = require('fastify')(); (async function () { - app.register(await server.createHandler()); + app.register(server.createHandler()); await app.listen(3000); })(); ``` diff --git a/packages/apollo-server-fastify/README.md b/packages/apollo-server-fastify/README.md index 230569ec2d3..39a322c49af 100644 --- a/packages/apollo-server-fastify/README.md +++ b/packages/apollo-server-fastify/README.md @@ -25,7 +25,7 @@ const server = new ApolloServer({ const app = require('fastify')(); (async function () { - app.register(await server.createHandler()); + app.register(server.createHandler()); await app.listen(3000); })(); ``` diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index f5d215b18a7..69bd4e72826 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -36,18 +36,20 @@ export class ApolloServer extends ApolloServerBase { return true; } - public async createHandler({ + public createHandler({ path, cors, disableHealthCheck, onHealthCheck, }: ServerRegistration = {}) { this.graphqlPath = path ? path : '/graphql'; - await this.willStart(); + const promiseWillStart = this.willStart(); return async ( app: FastifyInstance, ) => { + await promiseWillStart; + if (!disableHealthCheck) { app.get('/.well-known/apollo/server-health', async (req, res) => { // Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01 diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts index 68ae1e29d2c..1abf86e5ad0 100644 --- a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -40,7 +40,7 @@ describe('apollo-server-fastify', () => { async options => { server = new ApolloServer(options); app = fastify(); - app.register(await server.createHandler()); + app.register(server.createHandler()); await app.listen(port); return createServerInfo(server, app.server); }, @@ -64,7 +64,7 @@ describe('apollo-server-fastify', () => { server = new ApolloServer(serverOptions); app = fastify(); - app.register(await server.createHandler(options)); + app.register(server.createHandler(options)); await app.listen(port); return createServerInfo(server, app.server); diff --git a/packages/apollo-server-fastify/src/__tests__/datasource.test.ts b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts index 79d279f0c5f..d7c6d9f5f7d 100644 --- a/packages/apollo-server-fastify/src/__tests__/datasource.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts @@ -92,7 +92,7 @@ describe('apollo-server-fastify', () => { }); app = fastify(); - app.register(await server.createHandler()); + app.register(server.createHandler()); await app.listen(6667); const { url: uri } = createServerInfo(server, app.server); @@ -120,7 +120,7 @@ describe('apollo-server-fastify', () => { }); app = fastify(); - app.register(await server.createHandler()); + app.register(server.createHandler()); await app.listen(6668); const { url: uri } = createServerInfo(server, app.server); diff --git a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts index c85c0e44b83..d81d4d0126b 100644 --- a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts @@ -14,7 +14,7 @@ async function createApp(options: CreateAppOptions = {}) { (options.graphqlOptions as Config) || { schema: Schema }, ); - app.register(await server.createHandler()); + app.register(server.createHandler()); await app.listen(); return app.server; From fa621c60bb3108d168657cee169d5388b66d5cd6 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Dec 2018 14:52:35 +0200 Subject: [PATCH 06/11] (fastify) Tweaks to re-align with the parallel work in #2054. --- packages/apollo-server-fastify/package.json | 3 +-- .../apollo-server-fastify/src/ApolloServer.ts | 23 ++++++++++++++----- .../src/__tests__/ApolloServer.test.ts | 7 +++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json index dd7abeb687b..1fe504ba193 100644 --- a/packages/apollo-server-fastify/package.json +++ b/packages/apollo-server-fastify/package.json @@ -25,8 +25,7 @@ "node": ">=6" }, "dependencies": { - "@apollographql/apollo-upload-server": "^5.0.3", - "@apollographql/graphql-playground-html": "^1.6.4", + "@apollographql/graphql-playground-html": "^1.6.6", "apollo-server-core": "file:../apollo-server-core", "fastify-accepts": "^0.5.0", "fastify-cors": "^0.2.0", diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index 69bd4e72826..6c1ad2ffeab 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -3,10 +3,10 @@ import { Accepts } from 'accepts'; import { ApolloServerBase, PlaygroundRenderPageOptions, + processFileUploads, } from 'apollo-server-core'; import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; import { IncomingMessage, OutgoingMessage, Server } from 'http'; -import { processRequest as processFileUploads } from '@apollographql/apollo-upload-server'; import { graphqlFastify } from './fastifyApollo'; const fastJson = require('fast-json-stringify'); @@ -84,11 +84,22 @@ export class ApolloServer extends ApolloServerBase { reply.send(); }); - instance.addContentTypeParser( - 'multipart', - async (request: IncomingMessage) => - processFileUploads(request, this.uploadsConfig), - ); + if ( + this.uploadsConfig && + typeof processFileUploads !== 'undefined' && + typeof processFileUploads === 'function' + ) { + instance.addContentTypeParser( + 'multipart', + async (request: IncomingMessage) => + // This extra function guarding is being mandated by TypeScript. + // It certainly shouldn't be possible for this parse to even + // be present unless `processFileUploads` was a function when + // the handler was added (initially at server startup). + typeof processFileUploads === 'function' && + processFileUploads(request, this.uploadsConfig), + ); + } instance.route({ method: ['GET', 'POST'], diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts index 1abf86e5ad0..f676aa97f81 100644 --- a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -12,7 +12,7 @@ import { gql, AuthenticationError, Config } from 'apollo-server-core'; import { ApolloServer, ServerRegistration } from '../ApolloServer'; import { - atLeastMajorNodeVersion, + NODE_MAJOR_VERSION, testApolloServer, createServerInfo, } from 'apollo-server-integration-testsuite'; @@ -401,8 +401,9 @@ describe('apollo-server-fastify', () => { }); }); }); - // NODE: Intentionally skip file upload tests on Node.js 10 or higher. - (atLeastMajorNodeVersion(10) ? describe.skip : describe)( + // NODE: Skip Node.js 6, but only because `graphql-upload` + // doesn't support it. + (NODE_MAJOR_VERSION === 6 ? describe.skip : describe)( 'file uploads', () => { it('enabled uploads', async () => { From 99bf841220a4cebd7a402a893a8edb0d5cbdab45 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Dec 2018 14:53:00 +0200 Subject: [PATCH 07/11] (fastify): Use port 9999 rather than 8888 for tests. Because Gatsby. This specific port per integration is pretty brittle to begin with, but it does work. Currently, the fact that it works is facilitated by the fact that most people don't use 5555 (Hapi) and 6666 (Express) for anything. That said, the ever-popular Gatsby uses 8888 by default, so let's use 9999! --- .../apollo-server-fastify/src/__tests__/ApolloServer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts index f676aa97f81..356e005e3e1 100644 --- a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -29,7 +29,7 @@ const resolvers = { }, }; -const port = 8888; +const port = 9999; describe('apollo-server-fastify', () => { let server: ApolloServer; From 59873843a83f60f8eaf364d7fa82f2bec903cc2f Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Dec 2018 15:10:15 +0200 Subject: [PATCH 08/11] (fastify) Remove duplicative assertion in upload initialization. --- packages/apollo-server-fastify/src/ApolloServer.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index 6c1ad2ffeab..e82cae20c41 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -84,11 +84,7 @@ export class ApolloServer extends ApolloServerBase { reply.send(); }); - if ( - this.uploadsConfig && - typeof processFileUploads !== 'undefined' && - typeof processFileUploads === 'function' - ) { + if (typeof processFileUploads === 'function' && this.uploadsConfig) { instance.addContentTypeParser( 'multipart', async (request: IncomingMessage) => From 05158d3e0d410c2263f83659de01ec1d5a886dee Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Wed, 2 Jan 2019 14:17:14 +0100 Subject: [PATCH 09/11] (fastify) Implement fastify upload middleware --- .../apollo-server-fastify/src/ApolloServer.ts | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index e82cae20c41..09a9bc08156 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -1,14 +1,15 @@ import { renderPlaygroundPage } from '@apollographql/graphql-playground-html'; import { Accepts } from 'accepts'; import { - ApolloServerBase, + ApolloServerBase, FileUploadOptions, formatApolloErrors, PlaygroundRenderPageOptions, - processFileUploads, + processFileUploads } from 'apollo-server-core'; import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; import { IncomingMessage, OutgoingMessage, Server } from 'http'; import { graphqlFastify } from './fastifyApollo'; +const kMultipart = Symbol('multipart'); const fastJson = require('fast-json-stringify'); export interface ServerRegistration { @@ -27,6 +28,33 @@ const stringifyHealthCheck = fastJson({ }, }); +const fileUploadMiddleware = ( + uploadsConfig: FileUploadOptions, + server: ApolloServerBase, +) => ( + req: FastifyRequest, + reply: FastifyReply, + done: (err: Error | null, body?: any) => void +) => { + if ((req.req as any)[kMultipart] && typeof processFileUploads === 'function') { + processFileUploads(req.req, reply.res, uploadsConfig) + .then(body => { + req.body = body; + done(null); + }) + .catch(error => { + if (error.status && error.expose) reply.status(error.status); + + throw formatApolloErrors([error], { + formatter: server.requestOptions.formatError, + debug: server.requestOptions.debug, + }); + }); + } else { + done(null); + } +}; + export class ApolloServer extends ApolloServerBase { protected supportsSubscriptions(): boolean { return true; @@ -84,23 +112,8 @@ export class ApolloServer extends ApolloServerBase { reply.send(); }); - if (typeof processFileUploads === 'function' && this.uploadsConfig) { - instance.addContentTypeParser( - 'multipart', - async (request: IncomingMessage) => - // This extra function guarding is being mandated by TypeScript. - // It certainly shouldn't be possible for this parse to even - // be present unless `processFileUploads` was a function when - // the handler was added (initially at server startup). - typeof processFileUploads === 'function' && - processFileUploads(request, this.uploadsConfig), - ); - } - - instance.route({ - method: ['GET', 'POST'], - url: '/', - beforeHandler: ( + const beforeHandlers = [ + ( req: FastifyRequest, reply: FastifyReply, done: () => void, @@ -134,7 +147,24 @@ export class ApolloServer extends ApolloServerBase { } } done(); - }, + } + ]; + + if (typeof processFileUploads === 'function' && this.uploadsConfig) { + instance.addContentTypeParser( + 'multipart', + (request: IncomingMessage, done: (err: Error | null, body?: any) => void) => { + (request as any)[kMultipart] = true; + done(null); + } + ); + beforeHandlers.push(fileUploadMiddleware(this.uploadsConfig, this)); + } + + instance.route({ + method: ['GET', 'POST'], + url: '/', + beforeHandler: beforeHandlers, handler: await graphqlFastify(this.graphQLServerOptions.bind(this)), }); }, From 94e9a07cd2c0cd9c1be7bf3f03f33ba0e11911c5 Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Wed, 2 Jan 2019 14:20:07 +0100 Subject: [PATCH 10/11] (fastify) Fix linting issues --- .../apollo-server-fastify/src/ApolloServer.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index 09a9bc08156..d8a6f73d50c 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -1,9 +1,11 @@ import { renderPlaygroundPage } from '@apollographql/graphql-playground-html'; import { Accepts } from 'accepts'; import { - ApolloServerBase, FileUploadOptions, formatApolloErrors, + ApolloServerBase, + FileUploadOptions, + formatApolloErrors, PlaygroundRenderPageOptions, - processFileUploads + processFileUploads, } from 'apollo-server-core'; import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; import { IncomingMessage, OutgoingMessage, Server } from 'http'; @@ -34,9 +36,12 @@ const fileUploadMiddleware = ( ) => ( req: FastifyRequest, reply: FastifyReply, - done: (err: Error | null, body?: any) => void + done: (err: Error | null, body?: any) => void, ) => { - if ((req.req as any)[kMultipart] && typeof processFileUploads === 'function') { + if ( + (req.req as any)[kMultipart] && + typeof processFileUploads === 'function' + ) { processFileUploads(req.req, reply.res, uploadsConfig) .then(body => { req.body = body; @@ -147,16 +152,19 @@ export class ApolloServer extends ApolloServerBase { } } done(); - } + }, ]; if (typeof processFileUploads === 'function' && this.uploadsConfig) { instance.addContentTypeParser( 'multipart', - (request: IncomingMessage, done: (err: Error | null, body?: any) => void) => { + ( + request: IncomingMessage, + done: (err: Error | null, body?: any) => void, + ) => { (request as any)[kMultipart] = true; done(null); - } + }, ); beforeHandlers.push(fileUploadMiddleware(this.uploadsConfig, this)); } From 6ebb60a86c00cac96f5bbcf9004d4958334c961b Mon Sep 17 00:00:00 2001 From: Remy Korrelboom Date: Wed, 2 Jan 2019 14:43:45 +0100 Subject: [PATCH 11/11] (fastify) Update package-lock --- package-lock.json | 435 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 405 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a0e3c77c85..dfcd428f175 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1481,6 +1481,16 @@ "@types/node": "*" } }, + "@types/pino": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-4.16.1.tgz", + "integrity": "sha512-uYEhZ3jsuiYFsPcR34fbxVlrqzqphc+QQ3fU4rWR6PXH8ka2TKvPBjtkNqj8oBHouVGf4GCRfyPb7FG2TEtPZA==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*" + } + }, "@types/podium": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/podium/-/podium-1.0.0.tgz", @@ -1596,6 +1606,12 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abstract-logging": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-1.0.0.tgz", + "integrity": "sha1-i33q/TEFWbwo93ck3RuzAXcnjBs=", + "dev": true + }, "accept": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/accept/-/accept-3.0.2.tgz", @@ -2201,6 +2217,17 @@ "type-is": "^1.6.16" } }, + "apollo-server-fastify": { + "version": "file:packages/apollo-server-fastify", + "requires": { + "@apollographql/graphql-playground-html": "^1.6.6", + "apollo-server-core": "file:packages/apollo-server-core", + "fastify-accepts": "^0.5.0", + "fastify-cors": "^0.2.0", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0" + } + }, "apollo-server-hapi": { "version": "file:packages/apollo-server-hapi", "requires": { @@ -2321,7 +2348,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -2336,7 +2363,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2509,6 +2536,33 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "avvio": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-5.9.0.tgz", + "integrity": "sha512-bzgrSPRdU1T/AkhEuXWAA6cJCFA3zApLk+5fkpcQt4US9YAI52AFYnsGX1HSCF2bHSltEYfk7fbffYu4WnazmA==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "fastq": "^1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3409,7 +3463,7 @@ "dependencies": { "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -4033,6 +4087,12 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.0.0.tgz", + "integrity": "sha512-a8z8bkgHsAML+uHLqmMS83HHlpy3PvZOOuiTQqaa3wu8ZVg3h0hqHk6aCsGdOnZV2XMM/FRimNGjUh0KCcmHBw==", + "dev": true + }, "default-require-extensions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", @@ -4267,7 +4327,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -4291,7 +4351,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -4306,7 +4366,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -4409,7 +4469,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -4676,6 +4736,12 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fast-decode-uri-component": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.0.tgz", + "integrity": "sha512-WQSYVKn6tDW/3htASeUkrx5LcnuTENQIZQPCVlwdnvIJ7bYtSpoJYq38MgUJnx1CQIR1gjZ8HJxAEcN4gqugBg==", + "dev": true + }, "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", @@ -5008,17 +5074,157 @@ } } }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-json-stringify": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-1.10.0.tgz", + "integrity": "sha512-qO+GSdwCQHXJjoRbS/pYJzzz8BNUrCk0jdPhDg68mdIG/hOC0k66PUKFz300LAH42vNQkuPFvpwa0JV44ZG3Uw==", + "dev": true, + "requires": { + "ajv": "^6.5.4", + "deepmerge": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz", + "integrity": "sha512-QJYT/i0QYoiZBQ71ivxdyTqkwKkQ0oxACXHYxH2zYHJEgzi2LsbjgvtzTbLi1SZcF190Db2YP7I7eTsU2egOlw==", + "dev": true + }, + "fastify": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-1.13.0.tgz", + "integrity": "sha512-0aqsHEk2WtgGxPVDTOqDLv5XLHQE2EuH7eCq4XRLLnktLehNvr3/Afi/nEn6pPoLiVGMrbWHv4l1+wDhiSIFoA==", + "dev": true, + "requires": { + "@types/pino": "^4.16.0", + "abstract-logging": "^1.0.0", + "ajv": "^6.5.4", + "avvio": "^5.8.0", + "end-of-stream": "^1.4.1", + "fast-json-stringify": "^1.8.0", + "find-my-way": "^1.15.3", + "flatstr": "^1.0.8", + "light-my-request": "^3.0.0", + "middie": "^3.1.0", + "pino": "^4.17.3", + "proxy-addr": "^2.0.3", + "tiny-lru": "^1.6.1" + }, + "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "fastify-accepts": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fastify-accepts/-/fastify-accepts-0.5.0.tgz", + "integrity": "sha1-wXwgEnjyv8Ub+5P/5I78T8QP02M=", + "requires": { + "accepts": "^1.3.3", + "fastify-plugin": "^0.2.1" + } + }, + "fastify-cors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-0.2.0.tgz", + "integrity": "sha512-bw14FmjHm8oF4TDLkwj2TpssH6O2gE0NpsRqLe7F1Gh9Jf30Lx9ZzIznhqaAKOYS+LJqLIt5snurv7urgqYntA==", + "requires": { + "fastify-plugin": "^1.2.0", + "vary": "^1.1.2" + }, + "dependencies": { + "fastify-plugin": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-1.4.0.tgz", + "integrity": "sha512-l6uqDyBp3gBjLQRAi3j2NwSvlbe9LuqULZugnO9iRFfYHWd2SpsZBLI1l4Jakk0VMGfYlB322JPIPYh/2qSHig==", + "requires": { + "semver": "^5.5.0" + } + } + } + }, + "fastify-plugin": { + "version": "0.2.2", + "resolved": "http://registry.npmjs.org/fastify-plugin/-/fastify-plugin-0.2.2.tgz", + "integrity": "sha512-oRJdjdudgCkQQUARNeh2rkbxFAmj2OhCJSVBNBLUbhS0orF+IMQ4u/bc661N1jh/wDI2J+YKmXmmHSVFQI4e7A==", + "requires": { + "semver": "^5.4.1" + } + }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -5107,6 +5313,17 @@ } } }, + "find-my-way": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-1.17.0.tgz", + "integrity": "sha512-V/ROUAESJakNZXmvgJmaCwhB8d4M79/RgWWiKn4tu/6FGjiySYfJuYXip1rV7fORWEzbL0pmfg9smkRQ+tmXjg==", + "dev": true, + "requires": { + "fast-decode-uri-component": "^1.0.0", + "safe-regex": "^1.1.0", + "semver-store": "^0.3.0" + } + }, "find-npm-prefix": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", @@ -5128,6 +5345,12 @@ "locate-path": "^3.0.0" } }, + "flatstr": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.9.tgz", + "integrity": "sha512-qFlJnOBWDfIaunF54/lBqNKmXOI0HqNhu+mHkLmbaBXlS71PUd9OjFOdyevHt/aHoHB1+eW7eKHgRKOG5aHSpw==", + "dev": true + }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -5146,7 +5369,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5161,7 +5384,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -5251,7 +5474,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5266,7 +5489,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -5990,7 +6213,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -6054,7 +6277,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -6085,7 +6308,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -6103,7 +6326,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -6192,7 +6415,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -6264,7 +6487,7 @@ }, "globby": { "version": "8.0.1", - "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "dev": true, "requires": { @@ -8724,6 +8947,62 @@ } } }, + "light-my-request": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-3.1.0.tgz", + "integrity": "sha512-ZSFO3XnQNSKsHR/E2ZMga5btdiIa3sNoT6CZIZ8Hr1VHJWBNcRRurVYpQlaJqvQqwg3aOl09QpVOnjB9ajnYHQ==", + "dev": true, + "requires": { + "ajv": "^6.0.0", + "readable-stream": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "lint-staged": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.0.tgz", @@ -9538,7 +9817,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -9745,6 +10024,24 @@ "regex-cache": "^0.4.2" } }, + "middie": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/middie/-/middie-3.2.0.tgz", + "integrity": "sha512-anXJ0QJfQcgneQvcWAJBwVvNckRLI68zWNEUv/7/7z/Wb/UMFTHmugpM93T4Q75+DclC9FHdms8cTseDQEV3yA==", + "dev": true, + "requires": { + "path-to-regexp": "^2.0.0", + "reusify": "^1.0.2" + }, + "dependencies": { + "path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + } + } + }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", @@ -10049,7 +10346,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true } @@ -10473,7 +10770,7 @@ }, "p-is-promise": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, @@ -10623,7 +10920,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -10638,7 +10935,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -10779,6 +11076,41 @@ "pinkie": "^2.0.0" } }, + "pino": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/pino/-/pino-4.17.6.tgz", + "integrity": "sha512-LFDwmhyWLBnmwO/2UFbWu1jEGVDzaPupaVdx0XcZ3tIAx1EDEBauzxXf2S0UcFK7oe+X9MApjH0hx9U1XMgfCA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "fast-json-parse": "^1.0.3", + "fast-safe-stringify": "^1.2.3", + "flatstr": "^1.0.5", + "pino-std-serializers": "^2.0.0", + "pump": "^3.0.0", + "quick-format-unescaped": "^1.1.2", + "split2": "^2.2.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "pino-std-serializers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.3.0.tgz", + "integrity": "sha512-klfGoOsP6sJH7ON796G4xoUSx2fkpFgKHO4YVVO2zmz31jR+etzc/QzGJILaOIiCD6HTCFgkPx+XN8nk+ruqPw==", + "dev": true + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -11062,6 +11394,15 @@ } } }, + "quick-format-unescaped": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-1.1.2.tgz", + "integrity": "sha1-DKWB3jF0vs7yWsPC6JVjQjgdtpg=", + "dev": true, + "requires": { + "fast-safe-stringify": "^1.0.8" + } + }, "quick-lru": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", @@ -11454,6 +11795,12 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -11829,8 +12176,7 @@ "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "semver-compare": { "version": "1.0.0", @@ -11838,6 +12184,12 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==", + "dev": true + }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -12425,7 +12777,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -12555,7 +12907,7 @@ }, "tar": { "version": "2.2.1", - "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { @@ -12617,7 +12969,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -12639,7 +12991,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12654,7 +13006,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -12663,6 +13015,12 @@ } } }, + "tiny-lru": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-1.6.4.tgz", + "integrity": "sha512-Et+J3Css66XPSLWjLF9wmgbECsGiExlEL+jxsFerTQF6N6dpxswDTPAfIrAbQKO5c1uhgq2xvo5zMk1W+kBDNA==", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -12810,7 +13168,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -13064,6 +13422,23 @@ } } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, "urijs": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.1.tgz",