From 5b64cf9160c8a4774cf6c20f5f54cbd2223f0223 Mon Sep 17 00:00:00 2001 From: Leonid Buneev Date: Tue, 6 Nov 2018 20:28:25 +0100 Subject: [PATCH] apollo-server-azure-function v2 implementation. (#1753) Closes #1752. --- .DS_Store | Bin 0 -> 6148 bytes CHANGELOG.md | 1 + README.md | 3 + package-lock.json | 9 + package.json | 1 + .../apollo-server-azure-function/.npmignore | 6 + .../apollo-server-azure-function/README.md | 154 ++++++++++++++++ .../apollo-server-azure-function/package.json | 33 ++++ .../src/ApolloServer.ts | 165 ++++++++++++++++++ .../src/__tests__/azureFunctionApollo.test.ts | 155 ++++++++++++++++ .../src/azureFunctionApollo.ts | 87 +++++++++ .../src/azureFunctions.d.ts | 151 ++++++++++++++++ .../apollo-server-azure-function/src/index.ts | 24 +++ .../tsconfig.json | 12 ++ tsconfig.build.json | 1 + tsconfig.test.json | 1 + 16 files changed, 803 insertions(+) create mode 100644 .DS_Store create mode 100644 packages/apollo-server-azure-function/.npmignore create mode 100644 packages/apollo-server-azure-function/README.md create mode 100644 packages/apollo-server-azure-function/package.json create mode 100644 packages/apollo-server-azure-function/src/ApolloServer.ts create mode 100644 packages/apollo-server-azure-function/src/__tests__/azureFunctionApollo.test.ts create mode 100644 packages/apollo-server-azure-function/src/azureFunctionApollo.ts create mode 100644 packages/apollo-server-azure-function/src/azureFunctions.d.ts create mode 100644 packages/apollo-server-azure-function/src/index.ts create mode 100644 packages/apollo-server-azure-function/tsconfig.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..404efbf7d3aa1a1299d19c1fccca39ab024b6fda GIT binary patch literal 6148 zcmeHKISv9b477n_B^pY~e1RWC2wuPkI7I;xNQizb-o?`x9|dTkg9eQyXA;MgC{wK0 zBBImlb|Nwokp^xkHygTU`{o_%Wki8+oUxLu7m_W|QJat3oFhiCr!bUNB5 zjS5f!DnJFO02TPE0$E<0;a5-PVN`$${DT7aeJF6ln%D;Vrvrnx0KfslZkT&70W1~( z*2Fdt5ts%Q7*x#`LxYZZ$-J7_1_oU;n-9&KH9Hjb+i`yJbkQ2fkqS_OR|Wd999jLJ z!$0)@uOzOh02TNv1#~c5%%*r!*4EDBtkxFz5^gznxEbb7!QkZ>=;as-E5~C`io9ZT X?AOFL(CLUf9mt;n(}hL_eyzX*+7K0P literal 0 HcmV?d00001 diff --git a/CHANGELOG.md b/CHANGELOG.md index 53972eadc54..0bb0c701bbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Update `graphql-playground-html` to 1.7.8 - Allow an optional function to resolve the `rootValue`, passing the `DocumentNode` AST to determine the value. [PR #1555](https://github.com/apollographql/apollo-server/pull/1555) - Follow-up on the work in [PR #1516](https://github.com/apollographql/apollo-server/pull/1516) to also fix missing insertion cursor/caret when a custom GraphQL configuration is specified which doesn't specify its own `cursorShape` property. [PR #1607](https://github.com/apollographql/apollo-server/pull/1607) +- Azure functions support [Issue #1752](https://github.com/apollographql/apollo-server/issue/1752) [PR #1753](https://github.com/apollographql/apollo-server/pull/1753) - Allow JSON parsing in `RESTDataSource` of Content Type `application/hal+json`. [PR ##185](https://github.com/apollographql/apollo-server/pull/1853) - Add support for a `requestAgent` configuration parameter within the `engine` configuration. This can be utilized when a proxy is necessary to transmit tracing and metrics data to Apollo Engine. It accepts either an [`http.Agent`](https://nodejs.org/docs/latest-v8.x/api/http.html#http_class_http_agent) or [`https.Agent`](https://nodejs.org/docs/latest-v8.x/api/https.html#https_class_https_agent) and behaves the same as the `agent` parameter to Node.js' [`http.request`](https://nodejs.org/docs/latest-v8.x/api/http.html#http_http_request_options_callback). [PR #1879](https://github.com/apollographql/apollo-server/pull/1879) diff --git a/README.md b/README.md index a300a9dcf13..e9e134b64aa 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,11 @@ Often times, Apollo Server needs to be run with a particular integration. To sta - `koa` - `hapi` - `lambda` +- `azure-function` +- `cloud-function` - `cloudflare` + If a framework is not on this list and it should be supported, please open a PR. ### Express diff --git a/package-lock.json b/package-lock.json index fb06ade6a31..01258b50b8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2090,6 +2090,15 @@ "graphql-tools": "^4.0.0" } }, + "apollo-server-azure-function": { + "version": "file:packages/apollo-server-azure-function", + "requires": { + "@apollographql/graphql-playground-html": "^1.6.4", + "apollo-server-core": "file:packages/apollo-server-core", + "apollo-server-env": "file:packages/apollo-server-env", + "graphql-tools": "^4.0.0" + } + }, "apollo-server-cache-memcached": { "version": "file:packages/apollo-server-cache-memcached", "requires": { diff --git a/package.json b/package.json index 874a2aac2b2..c583bca315b 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "apollo-engine-reporting": "file:packages/apollo-engine-reporting", "apollo-engine-reporting-protobuf": "file:packages/apollo-engine-reporting-protobuf", "apollo-server": "file:packages/apollo-server", + "apollo-server-azure-function": "file:packages/apollo-server-azure-function", "apollo-server-cache-memcached": "file:packages/apollo-server-cache-memcached", "apollo-server-cache-redis": "file:packages/apollo-server-cache-redis", "apollo-server-caching": "file:packages/apollo-server-caching", diff --git a/packages/apollo-server-azure-function/.npmignore b/packages/apollo-server-azure-function/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-server-azure-function/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-server-azure-function/README.md b/packages/apollo-server-azure-function/README.md new file mode 100644 index 00000000000..89728cafcf3 --- /dev/null +++ b/packages/apollo-server-azure-function/README.md @@ -0,0 +1,154 @@ +--- +title: Azure Functions +description: Setting up Apollo Server with Azure Functions +--- + +This is the Azure functions 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/v2). [Read the CHANGELOG](https://github.com/apollographql/apollo-server/blob/master/CHANGELOG.md). + +```sh +npm install apollo-server-azure-functions@alpha graphql +``` + +## Writing azure function + +Azure functions currently support two runtime versions. This package assumes that function is running under **runtime 2.0**. + +Azure functions typically consist of at least 2 files - index.js (function handler definition) and function.json (function settings and bindings). +For more information about azure functions development model in general, refer to [official Azure functions docs](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node). + +Example index.js: + +```js +const { gql, ApolloServer } = require("apollo-server-azure-function"); + +// Construct a schema, using GraphQL schema language +const typeDefs = gql` + type Query { + hello: String + } +`; + +// A map of functions which return data for the schema. +const resolvers = { + Query: { + hello: () => "world" + } +}; + +const server = new ApolloServer({ typeDefs, resolvers }); + +module.exports = server.createHandler(); +``` + +Example function.json: +```json +{ + "disabled": false, + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} +``` + +It is important to set output binding name to '$return' for apollo-server-azure-function to work correctly. + +## Modifying the Azure Function Response (Enable CORS) + +To enable CORS the response HTTP headers need to be modified. To accomplish this use the `cors` option. + +```js +const { ApolloServer, gql } = require('apollo-server-azure-function'); + +// Construct a schema, using GraphQL schema language +const typeDefs = gql` + type Query { + hello: String + } +`; + +// Provide resolver functions for your schema fields +const resolvers = { + Query: { + hello: () => 'Hello world!', + }, +}; + +const server = new ApolloServer({ + typeDefs, + resolvers, +}); + +module.exports = server.createHandler({ + cors: { + origin: '*', + credentials: true, + }, +}); +``` + +To enable CORS response for requests with credentials (cookies, http authentication) the allow origin header must equal the request origin and the allow credential header must be set to true. + +```js +const { ApolloServer, gql } = require('apollo-server-azure-function'); + +// Construct a schema, using GraphQL schema language +const typeDefs = gql` + type Query { + hello: String + } +`; + +// Provide resolver functions for your schema fields +const resolvers = { + Query: { + hello: () => 'Hello world!', + }, +}; + +const server = new ApolloServer({ + typeDefs, + resolvers, +}); + +module.exports = server.createHandler({ + cors: { + origin: true, + credentials: true, + }, +}); +``` + +### Cors Options + +The options correspond to the [express cors configuration](https://github.com/expressjs/cors#configuration-options) with the following fields(all are optional): + +* `origin`: boolean | string | string[] +* `methods`: string | string[] +* `allowedHeaders`: string | string[] +* `exposedHeaders`: string | string[] +* `credentials`: boolean +* `maxAge`: number + +## 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-azure-function/package.json b/packages/apollo-server-azure-function/package.json new file mode 100644 index 00000000000..d8bf3c176e6 --- /dev/null +++ b/packages/apollo-server-azure-function/package.json @@ -0,0 +1,33 @@ +{ + "name": "apollo-server-azure-function", + "version": "2.2.0-alpha.2", + "description": "Production-ready Node.js GraphQL server for Azure Functions", + "keywords": [ "GraphQL", "Apollo", "Server", "Azure", "Javascript" ], + "author": "opensource@apollographql.com", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-azure-function" + }, + "homepage": "https://github.com/apollographql/apollo-server#readme", + "bugs": { + "url": "https://github.com/apollographql/apollo-server/issues" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=6" + }, + "dependencies": { + "@apollographql/graphql-playground-html": "^1.6.4", + "apollo-server-core": "file:../apollo-server-core", + "apollo-server-env": "file:../apollo-server-env", + "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-azure-function/src/ApolloServer.ts b/packages/apollo-server-azure-function/src/ApolloServer.ts new file mode 100644 index 00000000000..0a257b68c17 --- /dev/null +++ b/packages/apollo-server-azure-function/src/ApolloServer.ts @@ -0,0 +1,165 @@ +import { + HttpContext, + FunctionRequest, + FunctionResponse, +} from './azureFunctions'; +import { ApolloServerBase } from 'apollo-server-core'; +import { GraphQLOptions, Config } from 'apollo-server-core'; +import { + renderPlaygroundPage, + RenderPageOptions as PlaygroundRenderPageOptions, +} from '@apollographql/graphql-playground-html'; + +import { graphqlAzureFunction } from './azureFunctionApollo'; + +export interface CreateHandlerOptions { + cors?: { + origin?: boolean | string | string[]; + methods?: string | string[]; + allowedHeaders?: string | string[]; + exposedHeaders?: string | string[]; + credentials?: boolean; + maxAge?: number; + }; +} + +export class ApolloServer extends ApolloServerBase { + // If you feel tempted to add an option to this constructor. Please consider + // another place, since the documentation becomes much more complicated when + // the constructor is not longer shared between all integration + constructor(options: Config) { + if (process.env.ENGINE_API_KEY || options.engine) { + options.engine = { + sendReportsImmediately: true, + ...(typeof options.engine !== 'boolean' ? options.engine : {}), + }; + } + super(options); + } + + // This translates the arguments from the middleware into graphQL options It + // provides typings for the integration specific behavior, ideally this would + // be propagated with a generic to the super class + createGraphQLServerOptions( + request: FunctionRequest, + context: HttpContext, + ): Promise { + return super.graphQLServerOptions({ request, context }); + } + + public createHandler({ cors }: CreateHandlerOptions = { cors: undefined }) { + // We will kick off the `willStart` event once for the server, and then + // await it before processing any requests by incorporating its `await` into + // the GraphQLServerOptions function which is called before each request. + const promiseWillStart = this.willStart(); + + const corsHeaders: FunctionResponse['headers'] = {}; + + if (cors) { + if (cors.methods) { + if (typeof cors.methods === 'string') { + corsHeaders['Access-Control-Allow-Methods'] = cors.methods; + } else if (Array.isArray(cors.methods)) { + corsHeaders['Access-Control-Allow-Methods'] = cors.methods.join(','); + } + } + + if (cors.allowedHeaders) { + if (typeof cors.allowedHeaders === 'string') { + corsHeaders['Access-Control-Allow-Headers'] = cors.allowedHeaders; + } else if (Array.isArray(cors.allowedHeaders)) { + corsHeaders[ + 'Access-Control-Allow-Headers' + ] = cors.allowedHeaders.join(','); + } + } + + if (cors.exposedHeaders) { + if (typeof cors.exposedHeaders === 'string') { + corsHeaders['Access-Control-Expose-Headers'] = cors.exposedHeaders; + } else if (Array.isArray(cors.exposedHeaders)) { + corsHeaders[ + 'Access-Control-Expose-Headers' + ] = cors.exposedHeaders.join(','); + } + } + + if (cors.credentials) { + corsHeaders['Access-Control-Allow-Credentials'] = 'true'; + } + if (cors.maxAge) { + corsHeaders['Access-Control-Max-Age'] = cors.maxAge; + } + } + + return (context: HttpContext, req: FunctionRequest) => { + if (cors && cors.origin) { + if (typeof cors.origin === 'string') { + corsHeaders['Access-Control-Allow-Origin'] = cors.origin; + } else if ( + typeof cors.origin === 'boolean' || + (Array.isArray(cors.origin) && + cors.origin.includes( + req.headers['Origin'] || req.headers['origin'], + )) + ) { + corsHeaders['Access-Control-Allow-Origin'] = + req.headers['Origin'] || req.headers['origin']; + } + + if (!cors.allowedHeaders) { + corsHeaders['Access-Control-Allow-Headers'] = + req.headers['Access-Control-Request-Headers']; + } + } + + if (req.method === 'OPTIONS') { + context.done(null, { + body: '', + status: 204, + headers: corsHeaders, + }); + return; + } + + if (this.playgroundOptions && req.method === 'GET') { + const acceptHeader = req.headers['Accept'] || req.headers['accept']; + if (acceptHeader && acceptHeader.includes('text/html')) { + const path = req.originalUrl || '/'; + + const playgroundRenderPageOptions: PlaygroundRenderPageOptions = { + endpoint: path, + ...this.playgroundOptions, + }; + const body = renderPlaygroundPage(playgroundRenderPageOptions); + context.done(null, { + body: body, + status: 200, + headers: { + 'Content-Type': 'text/html', + ...corsHeaders, + }, + }); + return; + } + } + + const callbackFilter = (error?: any, output?: FunctionResponse) => { + context.done( + error, + output && { + ...output, + headers: { + ...output.headers, + ...corsHeaders, + }, + }, + ); + }; + graphqlAzureFunction(async () => { + await promiseWillStart; + return this.createGraphQLServerOptions(req, context); + })(context, req, callbackFilter); + }; + } +} diff --git a/packages/apollo-server-azure-function/src/__tests__/azureFunctionApollo.test.ts b/packages/apollo-server-azure-function/src/__tests__/azureFunctionApollo.test.ts new file mode 100644 index 00000000000..8a918178157 --- /dev/null +++ b/packages/apollo-server-azure-function/src/__tests__/azureFunctionApollo.test.ts @@ -0,0 +1,155 @@ +import { ApolloServer } from '../ApolloServer'; +import testSuite, { + schema as Schema, + CreateAppOptions, +} from 'apollo-server-integration-testsuite'; +import { Config } from 'apollo-server-core'; +import url from 'url'; +import { IncomingMessage, ServerResponse } from 'http'; + +const createAzureFunction = (options: CreateAppOptions = {}) => { + const server = new ApolloServer( + (options.graphqlOptions as Config) || { schema: Schema }, + ); + + const handler = server.createHandler(); + + return (req: IncomingMessage, res: ServerResponse) => { + // return 404 if path is /bogus-route to pass the test, azure doesn't have paths + if (req.url.includes('/bogus-route')) { + res.statusCode = 404; + return res.end(); + } + + let body = ''; + req.on('data', chunk => (body += chunk)); + req.on('end', () => { + const urlObject = url.parse(req.url, true); + const request = { + method: req.method, + body: body && JSON.parse(body), + path: req.url, + query: urlObject.query, + headers: req.headers, + }; + const context = { + done(error, result) { + if (error) throw error; + res.statusCode = result.status; + for (let key in result.headers) { + if (result.headers.hasOwnProperty(key)) { + res.setHeader(key, result.headers[key]); + } + } + res.write(result.body); + res.end(); + }, + }; + handler(context as any, request as any); + }); + }; +}; + +describe('integration:AzureFunctions', () => { + testSuite(createAzureFunction); + + it('can append CORS headers to GET request', () => { + const server = new ApolloServer({ schema: Schema }); + const handler = server.createHandler({ + cors: { + origin: 'CORSOrigin', + methods: ['GET', 'POST', 'PUT'], + allowedHeaders: 'AllowedCORSHeader1,AllowedCORSHeader1', + exposedHeaders: 'ExposedCORSHeader1,ExposedCORSHeader2', + credentials: true, + maxAge: 42, + }, + }); + const expectedResult = { + testString: 'it works', + }; + const query = { + query: 'query test{ testString }', + }; + const request = { + method: 'GET', + body: null, + path: '/graphql', + query: query, + headers: {}, + }; + const context = { + done(error, result) { + if (error) throw error; + expect(result.status).toEqual(200); + expect(result.body).toEqual(expectedResult); + expect(result.headers['Access-Control-Allow-Origin']).toEqual( + 'CORSOrigin', + ); + expect(result.headers['Access-Control-Allow-Methods']).toEqual( + 'GET,POST,PUT', + ); + expect(result.headers['Access-Control-Allow-Headers']).toEqual( + 'AllowedCORSHeader1,AllowedCORSHeader1', + ); + expect(result.headers['Access-Control-Expose-Headers']).toEqual( + 'ExposedCORSHeader1,ExposedCORSHeader2', + ); + expect(result.headers['Access-Control-Allow-Credentials']).toEqual( + 'true', + ); + expect(result.headers['Access-Control-Max-Age']).toEqual(42); + }, + }; + handler(context as any, request as any); + }); + + it('can handle OPTIONS request with CORS headers', () => { + const server = new ApolloServer({ schema: Schema }); + const handler = server.createHandler({ + cors: { + allowedHeaders: 'AllowedCORSHeader1,AllowedCORSHeader1', + }, + }); + const request = { + method: 'OPTIONS', + body: null, + path: '/graphql', + query: null, + headers: {}, + }; + const context = { + done(error, result) { + if (error) throw error; + expect(result.status).toEqual(204); + expect(result.headers['Access-Control-Allow-Headers']).toEqual( + 'AllowedCORSHeader1,AllowedCORSHeader1', + ); + }, + }; + handler(context as any, request as any); + }); + + it('can return playground html', () => { + const server = new ApolloServer({ schema: Schema }); + const handler = server.createHandler({}); + const request = { + method: 'GET', + body: null, + path: '/', + query: null, + headers: { + Accept: 'text/html', + }, + }; + const context = { + done(error, result) { + if (error) throw error; + expect(result.status).toEqual(200); + expect(result.body).toMatch(/GraphQL Playground/gi); + expect(result.headers['Content-Type']).toEqual('text/html'); + }, + }; + handler(context as any, request as any); + }); +}); diff --git a/packages/apollo-server-azure-function/src/azureFunctionApollo.ts b/packages/apollo-server-azure-function/src/azureFunctionApollo.ts new file mode 100644 index 00000000000..fd680501701 --- /dev/null +++ b/packages/apollo-server-azure-function/src/azureFunctionApollo.ts @@ -0,0 +1,87 @@ +import { + HttpContext, + FunctionRequest, + FunctionResponse, +} from './azureFunctions'; +import { + GraphQLOptions, + HttpQueryError, + runHttpQuery, +} from 'apollo-server-core'; +import { Headers } from 'apollo-server-env'; + +export interface AzureFunctionGraphQLOptionsFunction { + (request: FunctionRequest, context: HttpContext): + | GraphQLOptions + | Promise; +} + +export interface AzureFunctionHandler { + ( + context: HttpContext, + request: FunctionRequest, + callback: (err?: any, output?: FunctionResponse) => void, + ): void; +} + +export function graphqlAzureFunction( + options: GraphQLOptions | AzureFunctionGraphQLOptionsFunction, +): AzureFunctionHandler { + if (!options) { + throw new Error('Apollo Server requires options.'); + } + + if (arguments.length > 1) { + throw new Error( + `Apollo Server expects exactly one argument, got ${arguments.length}`, + ); + } + + const graphqlHandler: AzureFunctionHandler = ( + context, + request, + callback, + ): void => { + if (request.method === 'POST' && !request.body) { + callback(null, { + body: 'POST body missing.', + status: 500, + }); + return; + } + runHttpQuery([request, context], { + method: request.method, + options: options, + query: + request.method === 'POST' && request.body + ? request.body + : request.query, + request: { + url: request.originalUrl, + method: request.method, + headers: new Headers(request.headers), + }, + }).then( + ({ graphqlResponse, responseInit }) => { + callback(null, { + body: graphqlResponse, + status: 200, + headers: responseInit.headers, + }); + }, + (error: HttpQueryError) => { + if ('HttpQueryError' !== error.name) { + callback(error); + } else { + callback(null, { + body: error.message, + status: error.statusCode, + headers: error.headers, + }); + } + }, + ); + }; + + return graphqlHandler; +} diff --git a/packages/apollo-server-azure-function/src/azureFunctions.d.ts b/packages/apollo-server-azure-function/src/azureFunctions.d.ts new file mode 100644 index 00000000000..c9dbd78a553 --- /dev/null +++ b/packages/apollo-server-azure-function/src/azureFunctions.d.ts @@ -0,0 +1,151 @@ +export type Context = { + invocationId: string; + bindingData: any; + bindings: any; + + log: (text: any) => void; + done: (err?: any, output?: object) => void; +}; + +export type HttpMethod = + | 'GET' + | 'POST' + | 'PUT' + | 'DELETE' + | 'HEAD' + | 'OPTIONS' + | 'TRACE' + | 'CONNECT' + | 'PATCH'; + +export type FunctionRequest = { + originalUrl: string; + method: HttpMethod; + query: { [key: string]: string }; + headers: { [name: string]: string }; + body: any; + rawbody: any; +}; + +export type FunctionResponse = { + body?: any; + status?: number; + headers?: { + 'content-type'?: string; + 'content-length'?: HttpStatusCodes | number; + 'content-disposition'?: string; + 'content-encoding'?: string; + 'content-language'?: string; + 'content-range'?: string; + 'content-location'?: string; + 'content-md5'?: Buffer; + expires?: Date; + 'last-modified'?: Date; + [name: string]: any; + }; +}; + +export enum HttpStatusCodes { + // 1XX Informational + Continue = 100, + SwitchingProtocols = 101, + Processing = 102, + Checkpoint = 103, + + // 2XX Success + OK = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultiStatus = 207, + AlreadyReported = 208, + IMUsed = 226, + + // 3XX Redirection + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + SwitchProxy = 306, + TemporaryRedirect = 307, + PermanentRedirect = 308, + + // 4XX Client Error + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + PayloadTooLarge = 413, + URITooLong = 414, + UnsupportedMediaType = 415, + RangeNotSatisfiable = 416, + ExpectationFailed = 417, + ImATeapot = 418, + MethodFailure = 420, + EnhanceYourCalm = 420, + MisdirectedRequest = 421, + UnprocessableEntity = 422, + Locked = 423, + FailedDependency = 424, + UpgradeRequired = 426, + PreconditionRequired = 428, + TooManyRequests = 429, + RequestHeaderFieldsTooLarge = 431, + LoginTimeout = 440, + NoResponse = 444, + RetryWith = 449, + BlockedByWindowsParentalControls = 450, + UnavailableForLegalReasons = 451, + Redirect = 451, + SSLCertificateError = 495, + SSLCertificateRequired = 496, + HttpRequestSentToHttpsPort = 497, + ClientClosedRequest = 499, + InvalidToken = 498, + TokenRequired = 499, + RequestWasForbiddenByAntivirus = 499, + + // 5XX Server Error + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HttpVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + InsufficientStorage = 507, + LoopDetected = 508, + BandwidthLimitExceeded = 509, + NotExtended = 510, + NetworkAuthenticationRequired = 511, + UnknownError = 520, + WebServerIsDown = 521, + ConnectionTimedOut = 522, + OriginIsUnreachable = 523, + ATimeoutOccurred = 524, + SSLHandshakeFailed = 525, + InvalidSSLCertificate = 526, + SiteIsFrozen = 530, +} + +export type HttpContext = Context & { + res: FunctionResponse; +}; + +export interface FunctionHandler { + (context: HttpContext, request: FunctionRequest): void | Promise; +} diff --git a/packages/apollo-server-azure-function/src/index.ts b/packages/apollo-server-azure-function/src/index.ts new file mode 100644 index 00000000000..2ff184ef590 --- /dev/null +++ b/packages/apollo-server-azure-function/src/index.ts @@ -0,0 +1,24 @@ +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'; + +// ApolloServer integration. +export { ApolloServer, CreateHandlerOptions } from './ApolloServer'; diff --git a/packages/apollo-server-azure-function/tsconfig.json b/packages/apollo-server-azure-function/tsconfig.json new file mode 100644 index 00000000000..b9d700923fa --- /dev/null +++ b/packages/apollo-server-azure-function/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 39809dba39e..fccce9f4d10 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -10,6 +10,7 @@ { "path": "./packages/apollo-datasource-rest" }, { "path": "./packages/apollo-engine-reporting" }, { "path": "./packages/apollo-server" }, + { "path": "./packages/apollo-server-azure-function" }, { "path": "./packages/apollo-server-cache-memcached" }, { "path": "./packages/apollo-server-cache-redis" }, { "path": "./packages/apollo-server-caching" }, diff --git a/tsconfig.test.json b/tsconfig.test.json index c740e3dbf68..2270d895d6d 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -9,6 +9,7 @@ { "path": "./packages/apollo-datasource-rest/src/__tests__/" }, { "path": "./packages/apollo-engine-reporting/src/__tests__/" }, { "path": "./packages/apollo-server/src/__tests__/" }, + { "path": "./packages/apollo-server-azure-function/src/__tests__/" }, { "path": "./packages/apollo-server-cache-memcached/src/__tests__/" }, { "path": "./packages/apollo-server-cache-redis/src/__tests__/" }, { "path": "./packages/apollo-server-caching/src/__tests__/" },