Setup prettier (#724)

* Setup prettier and precommit hooks

* Format code with prettier

* Use husky because it works...

* Move prettier config to .prettierrc file

* Implement fixing markdown file formatting when running lint-fix script

* Format markdown files

* Add .json file formatting

* Fixes json file formatting

* Add pretteir linting step

* Remove tslint

* Use gitignore for prettier

* Fix linting errors

* Ignore submodule folder
This commit is contained in:
Laurin Quast 2018-01-09 00:08:01 +01:00 committed by James Baxley
parent e837e92fb2
commit df51fd90da
100 changed files with 2606 additions and 2399 deletions

View file

@ -7,7 +7,7 @@
TODO: TODO:
- [ ] Update CHANGELOG.md with your change (include reference to issue & this PR) * [ ] Update CHANGELOG.md with your change (include reference to issue & this PR)
- [ ] Make sure all of the significant new logic is covered by tests * [ ] Make sure all of the significant new logic is covered by tests
- [ ] Rebase your changes on master so that they can be merged easily * [ ] Rebase your changes on master so that they can be merged easily
- [ ] Make sure all tests and linter rules pass * [ ] Make sure all tests and linter rules pass

4
.prettierrc Normal file
View file

@ -0,0 +1,4 @@
{
"trailingComma": "all",
"singleQuote": true
}

View file

@ -1,24 +1,29 @@
# Changelog # Changelog
### vNEXT ### vNEXT
* Fix apollo-server-core runQuery breaks async_hooks tracking [PR #733](https://github.com/apollographql/apollo-server/pull/733) * Fix apollo-server-core runQuery breaks async_hooks tracking [PR #733](https://github.com/apollographql/apollo-server/pull/733)
### v1.3.0 ### v1.3.0
* Added support for the vhost option for Hapi [PR #611](https://github.com/apollographql/apollo-server/pull/611) * Added support for the vhost option for Hapi [PR #611](https://github.com/apollographql/apollo-server/pull/611)
* Include readme for npm packages * Include readme for npm packages
* Update peerDependencies version for micro [PR #671](https://github.com/apollographql/apollo-server/pull/671) * Update peerDependencies version for micro [PR #671](https://github.com/apollographql/apollo-server/pull/671)
* Corrected the hapi example both in the README.md [PR #689] [Issue #689] * Corrected the hapi example both in the README.md [PR #689][issue #689]
* Change GraphqlOptions to also accept context as a function [PR #679](https://github.com/apollographql/apollo-server/pull/679) * Change GraphqlOptions to also accept context as a function [PR #679](https://github.com/apollographql/apollo-server/pull/679)
### v1.1.6 ### v1.1.6
* Fixes bug where CORS would not allow `Access-Control-Allow-Origin: *` with credential 'include', changed to 'same-origin' [Issue #514](https://github.com/apollographql/apollo-server/issues/514) * Fixes bug where CORS would not allow `Access-Control-Allow-Origin: *` with credential 'include', changed to 'same-origin' [Issue #514](https://github.com/apollographql/apollo-server/issues/514)
* Update apollo-server-lambda README to reflect new package name. * Update apollo-server-lambda README to reflect new package name.
* Add support for connectionParams in GraphiQL plugin options [#452](https://github.com/apollographql/apollo-server/issues/452) [PR 548](https://github.com/apollographql/apollo-server/pull/548) * Add support for connectionParams in GraphiQL plugin options [#452](https://github.com/apollographql/apollo-server/issues/452) [PR 548](https://github.com/apollographql/apollo-server/pull/548)
### v1.1.2 ### v1.1.2
* Fixed bug with no URL query params with GraphiQL on Lambda [Issue #504](https://github.com/apollographql/apollo-server/issues/504) [PR #512](https://github.com/apollographql/apollo-server/pull/503) * Fixed bug with no URL query params with GraphiQL on Lambda [Issue #504](https://github.com/apollographql/apollo-server/issues/504) [PR #512](https://github.com/apollographql/apollo-server/pull/503)
### v1.1.1 ### v1.1.1
* Added support for Azure Functions [#503](https://github.com/apollographql/apollo-server/pull/503) * Added support for Azure Functions [#503](https://github.com/apollographql/apollo-server/pull/503)
### v1.1.0 ### v1.1.0
@ -36,47 +41,61 @@
because it's a breaking change that shouldn't have been a patch update. because it's a breaking change that shouldn't have been a patch update.
### v1.0.2 ### v1.0.2
* Rename packages from graphql-server- to apollo-server- [#465](https://github.com/apollographql/apollo-server/pull/465). We'll continue to publish `graphql-server-` packages that depend on the renamed `apollo-server-` packages for the time being, to ensure backwards compatibility. * Rename packages from graphql-server- to apollo-server- [#465](https://github.com/apollographql/apollo-server/pull/465). We'll continue to publish `graphql-server-` packages that depend on the renamed `apollo-server-` packages for the time being, to ensure backwards compatibility.
### v1.0.1 ### v1.0.1
* Fix Express package not calling the callback on completion ([@chemdrew](https://github.com/chemdrew)) in [#463](https://github.com/apollographql/graphql-server/pull/463) * Fix Express package not calling the callback on completion ([@chemdrew](https://github.com/chemdrew)) in [#463](https://github.com/apollographql/graphql-server/pull/463)
### v1.0.0 ### v1.0.0
* Add package readmes for Express, Hapi, Koa, Restify ([@helfer](https://github.com/helfer)) in [#442](https://github.com/apollographql/graphql-server/pull/442) * Add package readmes for Express, Hapi, Koa, Restify ([@helfer](https://github.com/helfer)) in [#442](https://github.com/apollographql/graphql-server/pull/442)
* Updated & fixed typescript typings ([@helfer](https://github.com/helfer)) in [#440](https://github.com/apollographql/graphql-server/pull/440) * Updated & fixed typescript typings ([@helfer](https://github.com/helfer)) in [#440](https://github.com/apollographql/graphql-server/pull/440)
### v0.9.0 ### v0.9.0
* Allow GraphiQLOptions to be a function ([@NeoPhi](https://github.com/NeoPhi)) on [#426](https://github.com/apollographql/graphql-server/pull/426) * Allow GraphiQLOptions to be a function ([@NeoPhi](https://github.com/NeoPhi)) on [#426](https://github.com/apollographql/graphql-server/pull/426)
### v0.8.5 ### v0.8.5
* Fix: graphql-server-micro now properly returns response promises [#401](https://github.com/apollographql/graphql-server/pull/401) * Fix: graphql-server-micro now properly returns response promises [#401](https://github.com/apollographql/graphql-server/pull/401)
### v0.8.4 ### v0.8.4
### v0.8.3 ### v0.8.3
### v0.8.2 ### v0.8.2
* Fix issue with auto-updating dependencies that caused fibers to update accidentally ([@helfer](https://github.com/helfer)) on [#425](https://github.com/apollographql/graphql-server/pull/425) * Fix issue with auto-updating dependencies that caused fibers to update accidentally ([@helfer](https://github.com/helfer)) on [#425](https://github.com/apollographql/graphql-server/pull/425)
### v0.8.1 ### v0.8.1
* **Security Fix** Ensure queries submitted via HTTP GET run through validation ([@DxCx](https://github.com/DxCx)) on [#424](https://github.com/apollographql/graphql-server/pull/424) * **Security Fix** Ensure queries submitted via HTTP GET run through validation ([@DxCx](https://github.com/DxCx)) on [#424](https://github.com/apollographql/graphql-server/pull/424)
### v0.8.0 ### v0.8.0
* Persist `window.location.hash` on URL updates [#386](https://github.com/apollographql/graphql-server/issues/386) * Persist `window.location.hash` on URL updates [#386](https://github.com/apollographql/graphql-server/issues/386)
* Added support for `graphql-js` > 0.10.0 [#407](https://github.com/apollographql/graphql-server/pull/407) * Added support for `graphql-js` > 0.10.0 [#407](https://github.com/apollographql/graphql-server/pull/407)
* Updated `subscriptions-transport-ws` for GraphiQL with subscriptions [#407](https://github.com/apollographql/graphql-server/pull/407) * Updated `subscriptions-transport-ws` for GraphiQL with subscriptions [#407](https://github.com/apollographql/graphql-server/pull/407)
### v0.7.2 ### v0.7.2
* Fix include passHeader field that was accidentally removed * Fix include passHeader field that was accidentally removed
### v0.7.1 ### v0.7.1
* Fix graphiql fetcher to use endpointURL parameter instead of hardcoded URI.[#365](https://github.com/apollographql/graphql-server/issues/356) * Fix graphiql fetcher to use endpointURL parameter instead of hardcoded URI.[#365](https://github.com/apollographql/graphql-server/issues/356)
### v0.7.0 ### v0.7.0
* Add Zeit Micro Integration [#324](https://github.com/apollographql/graphql-server/issues/324) * Add Zeit Micro Integration [#324](https://github.com/apollographql/graphql-server/issues/324)
* add support for subscriptionURL to GraphiQL ([@urigo](https://github.com/urigo) on [#320](https://github.com/apollostack/graphql-server/pull/320) * add support for subscriptionURL to GraphiQL ([@urigo](https://github.com/urigo) on [#320](https://github.com/apollostack/graphql-server/pull/320)
* Restify: Fix for calling next() ([@jadkap](https://github.com/jadkap)) on [#285](https://github.com/apollostack/graphql-server/pull/285) * Restify: Fix for calling next() ([@jadkap](https://github.com/jadkap)) on [#285](https://github.com/apollostack/graphql-server/pull/285)
* **Breaking:** Update all dependencies [#329](https://github.com/apollographql/graphql-server/issues/329) * **Breaking:** Update all dependencies [#329](https://github.com/apollographql/graphql-server/issues/329)
### v0.6.0 ### v0.6.0
* Add AWS Lambda Integration [PR #247](https://github.com/apollostack/graphql-server/pull/247) * Add AWS Lambda Integration [PR #247](https://github.com/apollostack/graphql-server/pull/247)
* Update GraphiQL to version 0.9.1 ([@ephemer](https://github.com/ephemer)) on [#293](https://github.com/apollostack/graphql-server/pull/293) * Update GraphiQL to version 0.9.1 ([@ephemer](https://github.com/ephemer)) on [#293](https://github.com/apollostack/graphql-server/pull/293)
* **Restify integration** ([@joelgriffith](https://github.com/joelgriffith)) on [#189](https://github.com/apollostack/graphql-server/pull/189) * **Restify integration** ([@joelgriffith](https://github.com/joelgriffith)) on [#189](https://github.com/apollostack/graphql-server/pull/189)
@ -86,12 +105,15 @@
* Allow graphql@0.9.0 as peerDependency ([@Chris-R3](https://github.com/Chris-R3)) on [PR #278](https://github.com/apollostack/graphql-server/pull/278) * Allow graphql@0.9.0 as peerDependency ([@Chris-R3](https://github.com/Chris-R3)) on [PR #278](https://github.com/apollostack/graphql-server/pull/278)
### v0.5.1 ### v0.5.1
* add support for HTTP GET Method ([@DxCx](https://github.com/DxCx)) on [#180](https://github.com/apollostack/graphql-server/pull/180) * add support for HTTP GET Method ([@DxCx](https://github.com/DxCx)) on [#180](https://github.com/apollostack/graphql-server/pull/180)
### v0.5.0 ### v0.5.0
* Switch graphql typings for typescript to @types/graphql [#260](https://github.com/apollostack/graphql-server/pull/260) * Switch graphql typings for typescript to @types/graphql [#260](https://github.com/apollostack/graphql-server/pull/260)
### v0.4.4 ### v0.4.4
* Update GraphiQL to version 0.8.0 ([@DxCx](https://github.com/DxCx)) on [#192](https://github.com/apollostack/graphql-server/pull/192) * Update GraphiQL to version 0.8.0 ([@DxCx](https://github.com/DxCx)) on [#192](https://github.com/apollostack/graphql-server/pull/192)
* Upgrade to GraphQL-js 0.8.1. * Upgrade to GraphQL-js 0.8.1.
@ -118,12 +140,15 @@
* Clone context object for each query in a batch. * Clone context object for each query in a batch.
### v0.3.2 ### v0.3.2
* Added missing exports for hapi integration ([@nnance](https://github.com/nnance)) in [PR #152](https://github.com/apollostack/apollo-server/pull/152) * Added missing exports for hapi integration ([@nnance](https://github.com/nnance)) in [PR #152](https://github.com/apollostack/apollo-server/pull/152)
### v0.3.1 ### v0.3.1
* Fixed dependency issue with boom package that affected the hapi integration. ([@sammkj](https://github.com/sammkj) in [#150](https://github.com/apollostack/apollo-server/pull/150)) * Fixed dependency issue with boom package that affected the hapi integration. ([@sammkj](https://github.com/sammkj) in [#150](https://github.com/apollostack/apollo-server/pull/150))
### v0.3.0 ### v0.3.0
* Refactor Hapi integration to improve the API and make the plugins more idiomatic. ([@nnance](https://github.com/nnance)) in * Refactor Hapi integration to improve the API and make the plugins more idiomatic. ([@nnance](https://github.com/nnance)) in
[PR #127](https://github.com/apollostack/apollo-server/pull/127) [PR #127](https://github.com/apollostack/apollo-server/pull/127)
* Fixed query batching with Hapi integration. Issue #123 ([@nnance](https://github.com/nnance)) in * Fixed query batching with Hapi integration. Issue #123 ([@nnance](https://github.com/nnance)) in
@ -141,17 +166,21 @@
* Allow to pass custom headers in GraphiQL ([@nicolaslopezj](https://github.com/nicolaslopezj) in [#133](https://github.com/apollostack/apollo-server/pull/133)). * Allow to pass custom headers in GraphiQL ([@nicolaslopezj](https://github.com/nicolaslopezj) in [#133](https://github.com/apollostack/apollo-server/pull/133)).
### v0.2.6 ### v0.2.6
* Expose the OperationStore as part of the public API. ([@nnance](https://github.com/nnance)) * Expose the OperationStore as part of the public API. ([@nnance](https://github.com/nnance))
* Support adding parsed operations to the OperationStore. ([@nnance](https://github.com/nnance)) * Support adding parsed operations to the OperationStore. ([@nnance](https://github.com/nnance))
* Expose ApolloOptions as part of the public API. * Expose ApolloOptions as part of the public API.
### v0.2.5 ### v0.2.5
* Made promise compatible with fibers ([@benjamn](https://github.com/benjamn) in [#92](https://github.com/apollostack/apollo-server/pull/92)) * Made promise compatible with fibers ([@benjamn](https://github.com/benjamn) in [#92](https://github.com/apollostack/apollo-server/pull/92))
### v0.2.2 ### v0.2.2
* Log server events such as request start etc. with logFunction ([@helfer](https://github.com/helfer) in [#78](https://github.com/apollostack/apollo-server/pull/78)) * Log server events such as request start etc. with logFunction ([@helfer](https://github.com/helfer) in [#78](https://github.com/apollostack/apollo-server/pull/78))
### v0.2.1 ### v0.2.1
* Complete refactor of Apollo Server using TypeScript. PR [#41](https://github.com/apollostack/apollo-server/pull/41) * Complete refactor of Apollo Server using TypeScript. PR [#41](https://github.com/apollostack/apollo-server/pull/41)
* Added Hapi integration ([@nnance](https://github.com/nnance) in [#46](https://github.com/apollostack/apollo-server/pull/46)) * Added Hapi integration ([@nnance](https://github.com/nnance) in [#46](https://github.com/apollostack/apollo-server/pull/46))
* Added Koa integration ([@HriBB](https://github.com/HriBB) in [#59](https://github.com/apollostack/apollo-server/pull/59)) * Added Koa integration ([@HriBB](https://github.com/HriBB) in [#59](https://github.com/apollostack/apollo-server/pull/59))
@ -166,8 +195,8 @@
* Added `formatRequest` and `formatResponse` functions to apollo options. * Added `formatRequest` and `formatResponse` functions to apollo options.
* Removed support for shorthand schema definitions, connectors and mocks (use `graphql-tools` instead) * Removed support for shorthand schema definitions, connectors and mocks (use `graphql-tools` instead)
### v0.1.5 ### v0.1.5
* BUG: Fixed a spelling error with `tracer.submit()` from PR [#26](https://github.com/apollostack/apollo-server/pull/26) * BUG: Fixed a spelling error with `tracer.submit()` from PR [#26](https://github.com/apollostack/apollo-server/pull/26)
in PR [#31](https://github.com/apollostack/apollo-server/pull/31) in PR [#31](https://github.com/apollostack/apollo-server/pull/31)

View file

@ -57,8 +57,8 @@ Once there is a consensus on the need for a new feature, proceed as listed below
This includes: This includes:
- Big bug fixes * Big bug fixes
- New features * New features
For significant changes to a repository, its important to settle on a design before starting on the implementation. This way, we can make sure that major improvements get the care and attention they deserve. Since big changes can be risky and might not always get merged, its good to reduce the amount of possible wasted effort by agreeing on an implementation design/plan first. For significant changes to a repository, its important to settle on a design before starting on the implementation. This way, we can make sure that major improvements get the care and attention they deserve. Since big changes can be risky and might not always get merged, its good to reduce the amount of possible wasted effort by agreeing on an implementation design/plan first.
@ -85,5 +85,4 @@ Its important that every piece of code in Apollo packages is reviewed by at l
If you want to contribute to Apollo server, but aren't quite sure where to start, take a look at the [roadmap and design docs](./ROADMAP.md). Just pick one of the upcoming features that you're interested in, and start working on it. If the design doc isn't clear enough (which it probably won't be), open an issue thread so we can discuss it. If you want to contribute to Apollo server, but aren't quite sure where to start, take a look at the [roadmap and design docs](./ROADMAP.md). Just pick one of the upcoming features that you're interested in, and start working on it. If the design doc isn't clear enough (which it probably won't be), open an issue thread so we can discuss it.
Last but not least, make sure to join the [Apollo Slack channel](http://slack.apollostack.com), where there are lots of other friendly contributors to talk to. Last but not least, make sure to join the [Apollo Slack channel](http://slack.apollostack.com), where there are lots of other friendly contributors to talk to.

View file

@ -10,7 +10,6 @@ GraphQL Server consists of three parts:
At the core of GraphQL Server is a function called `runQuery`, which handles parsing, validating and executing queries. Its interface is generic in order to allow for integrations with different Node.js server frameworks. Extensions provide useful functionality that can be shared between different integrations. At the core of GraphQL Server is a function called `runQuery`, which handles parsing, validating and executing queries. Its interface is generic in order to allow for integrations with different Node.js server frameworks. Extensions provide useful functionality that can be shared between different integrations.
### Core ### Core
The main goals of GraphQL Server are (in order of priority): The main goals of GraphQL Server are (in order of priority):
@ -23,16 +22,17 @@ The main goals of GraphQL Server are (in order of priority):
GraphQL Server should come with a set of integrations for different Node.js server frameworks: GraphQL Server should come with a set of integrations for different Node.js server frameworks:
- Express * Express
- Hapi * Hapi
- Connect * Connect
- Koa * Koa
- Restify * Restify
- ... * ...
Framework integrations take care of parsing requests, submitting them to GraphQL Servers core runQuery function, and sending the response back to the client. These integrations should accept requests over HTTP, websockets or other means, then invoke `runQuery` as appropriate, and return the result. They should be written in such a way that makes it easy to add features, such as batched queries, subscriptions etc. Framework integrations take care of parsing requests, submitting them to GraphQL Servers core runQuery function, and sending the response back to the client. These integrations should accept requests over HTTP, websockets or other means, then invoke `runQuery` as appropriate, and return the result. They should be written in such a way that makes it easy to add features, such as batched queries, subscriptions etc.
Framework integrations should hide all transport-specific (eg. setting headers) and framework-specific things (eg. registering a route) from the core functions. Framework integrations should hide all transport-specific (eg. setting headers) and framework-specific things (eg. registering a route) from the core functions.
### Modules ### Modules
Things that are not part of runQuerys tasks, but are GraphQL specific (such as providing a bundle for the GraphiQL UI, generating a schema, storing prepared queries, etc.) should be implemented in another core module of GraphQL Server that lives alongside runQuery, or be imported from graphql-tools or other related packages. Things that are not part of runQuerys tasks, but are GraphQL specific (such as providing a bundle for the GraphiQL UI, generating a schema, storing prepared queries, etc.) should be implemented in another core module of GraphQL Server that lives alongside runQuery, or be imported from graphql-tools or other related packages.

View file

@ -31,14 +31,15 @@ Apollo Server is super easy to set up. Just `npm install apollo-server-<variant>
Just run `npm install --save apollo-server-<variant>` and you're good to go! Just run `npm install --save apollo-server-<variant>` and you're good to go!
where `<variant>` is one of the following: where `<variant>` is one of the following:
- `express`
- `koa` * `express`
- `hapi` * `koa`
- `restify` * `hapi`
- `lambda` * `restify`
- `micro` * `lambda`
- `azure-functions` * `micro`
- `adonis` * `azure-functions`
* `adonis`
### Express ### Express
@ -60,6 +61,7 @@ app.listen(PORT);
``` ```
### Connect ### Connect
```js ```js
import connect from 'connect'; import connect from 'connect';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
@ -125,6 +127,7 @@ StartServer();
``` ```
### Koa ### Koa
```js ```js
import koa from 'koa'; // koa@2 import koa from 'koa'; // koa@2
import koaRouter from 'koa-router'; // koa-router@next import koaRouter from 'koa-router'; // koa-router@next
@ -147,6 +150,7 @@ app.listen(PORT);
``` ```
### Restify ### Restify
```js ```js
import restify from 'restify'; import restify from 'restify';
import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify'; import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify';
@ -154,7 +158,7 @@ import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify';
const PORT = 3000; const PORT = 3000;
const server = restify.createServer({ const server = restify.createServer({
title: 'Apollo Server' title: 'Apollo Server',
}); });
const graphQLOptions = { schema: myGraphQLSchema }; const graphQLOptions = { schema: myGraphQLSchema };
@ -175,7 +179,7 @@ server.listen(PORT, () => console.log(`Listening on ${PORT}`));
Lambda function should be run with [Node.js 4.3 or v6.1](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-using-old-runtime.html#nodejs-prog-model-runtime-support-policy). Requires an API Gateway with Lambda Proxy Integration. Lambda function should be run with [Node.js 4.3 or v6.1](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-using-old-runtime.html#nodejs-prog-model-runtime-support-policy). Requires an API Gateway with Lambda Proxy Integration.
```js ```js
var server = require("apollo-server-lambda"); var server = require('apollo-server-lambda');
exports.handler = server.graphqlLambda({ schema: myGraphQLSchema }); exports.handler = server.graphqlLambda({ schema: myGraphQLSchema });
``` ```
@ -185,7 +189,7 @@ exports.handler = server.graphqlLambda({ schema: myGraphQLSchema });
Requires the [Micro](https://github.com/zeit/micro) module Requires the [Micro](https://github.com/zeit/micro) module
```js ```js
const server = require("apollo-server-micro"); const server = require('apollo-server-micro');
module.exports = server.microGraphql({ schema: myGraphQLSchema }); module.exports = server.microGraphql({ schema: myGraphQLSchema });
``` ```

View file

@ -2,7 +2,6 @@
This document contains a rough outline of a roadmap and a few designs for future features in Apollo Server. Contributions are very welcome! Feel free to pick an of the upcoming features and start implementing, and don't hold back with questions or ideas! This document contains a rough outline of a roadmap and a few designs for future features in Apollo Server. Contributions are very welcome! Feel free to pick an of the upcoming features and start implementing, and don't hold back with questions or ideas!
## Roadmap ## Roadmap
### Completed ### Completed
@ -32,33 +31,27 @@ This document contains a rough outline of a roadmap and a few designs for future
* Support for @defer, @stream and @live directives * Support for @defer, @stream and @live directives
## Proposed designs ## Proposed designs
### Error handling ### Error handling
GraphQL errors currently get swallowed on the server and formatted before they are sent to the client. This can make debugging difficult. To make getting started easier, apollo server should have a default logging functions that prints errors (including stack traces) to the server console. The default log function should only be used if a log function is not explicitly provided. GraphQL errors currently get swallowed on the server and formatted before they are sent to the client. This can make debugging difficult. To make getting started easier, apollo server should have a default logging functions that prints errors (including stack traces) to the server console. The default log function should only be used if a log function is not explicitly provided.
### Support for query timeouts ### Support for query timeouts
Query timeouts can be implemented by decorating the resolve functions, in a similar fashion to how `graphql-tracer` currently works (see [this function](https://github.com/apollostack/graphql-tracer/blob/71fd73f4463e6ee7ad87d77fd2819e81f2859d56/src/Tracer.js#L176)). A timeout will be set for the entire query by writing the time by which the query must end to the context. Each resolver will be decorated with a function that sets a timeout which will reject the resolver's promise with a timeout error no later than the end time written to the context. If the resolver returns before the timeout, execution continues as normal. See [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) for how to implement this pattern. Query timeouts can be implemented by decorating the resolve functions, in a similar fashion to how `graphql-tracer` currently works (see [this function](https://github.com/apollostack/graphql-tracer/blob/71fd73f4463e6ee7ad87d77fd2819e81f2859d56/src/Tracer.js#L176)). A timeout will be set for the entire query by writing the time by which the query must end to the context. Each resolver will be decorated with a function that sets a timeout which will reject the resolver's promise with a timeout error no later than the end time written to the context. If the resolver returns before the timeout, execution continues as normal. See [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) for how to implement this pattern.
### Websocket transport ### Websocket transport
Websockets will be useful to implement many of the future reactive features of GraphQL, like subscriptions and @live. Websockets will be useful to implement many of the future reactive features of GraphQL, like subscriptions and @live.
A first implementation could simply use the [ws library](https://github.com/websockets/ws), take queries sent in exactly the same format as they are currently sent over HTTP, pass them on to the core `runQuery` function and return the result over the websocket. Care must be taken to identify each request with a unique ID so the response can be sent with that same ID. Where the Express integration currently sends back HTTP error codes, the websocket implementation should omit those, and just send back JSON, for example `{ error: 'Invalid options provided to Apollo Server' }`. A first implementation could simply use the [ws library](https://github.com/websockets/ws), take queries sent in exactly the same format as they are currently sent over HTTP, pass them on to the core `runQuery` function and return the result over the websocket. Care must be taken to identify each request with a unique ID so the response can be sent with that same ID. Where the Express integration currently sends back HTTP error codes, the websocket implementation should omit those, and just send back JSON, for example `{ error: 'Invalid options provided to Apollo Server' }`.
### Subscriptions ### Subscriptions
(for reference on what GraphQL subscriptions are, see [here](https://medium.com/apollo-stack/new-features-in-graphql-batch-defer-stream-live-and-subscribe-7585d0c28b07)) (for reference on what GraphQL subscriptions are, see [here](https://medium.com/apollo-stack/new-features-in-graphql-batch-defer-stream-live-and-subscribe-7585d0c28b07))
Technically subscriptions are a different type of GraphQL operation (in addition to query and mutation), but in an initial version they could be implemented without modifications to graphql-js, by setting up poll-and-diff on the server. Upon receiving a subscription, the server first executes it as a normal query, but then adds it to a list of active subscriptions. Depending on whether updates happen in-band or out-of-band, subscribed queries are either re-run periodically, or when a mutation happens. The initial implementation should be done with polling. After that we can get arbitrarily fancy with things like invalidations, Mongo oplog-tailing etc. Technically subscriptions are a different type of GraphQL operation (in addition to query and mutation), but in an initial version they could be implemented without modifications to graphql-js, by setting up poll-and-diff on the server. Upon receiving a subscription, the server first executes it as a normal query, but then adds it to a list of active subscriptions. Depending on whether updates happen in-band or out-of-band, subscribed queries are either re-run periodically, or when a mutation happens. The initial implementation should be done with polling. After that we can get arbitrarily fancy with things like invalidations, Mongo oplog-tailing etc.
### Support for @defer, @live and @stream ### Support for @defer, @live and @stream
Support for the defer, stream and live directives will require replacing or modifying graphql-js `execute` function. The `execute` function would have to be changed to return an observable instead of a promise, and support for each of the directives would have to be built directly into the execution engine. Support for the defer, stream and live directives will require replacing or modifying graphql-js `execute` function. The `execute` function would have to be changed to return an observable instead of a promise, and support for each of the directives would have to be built directly into the execution engine.

View file

@ -22,6 +22,7 @@
"scripts": { "scripts": {
"start": "hexo serve", "start": "hexo serve",
"build": "hexo generate", "build": "hexo generate",
"develop-theme": "nodemon -x 'rm db.json; hexo serve' -w assets/ -w code/ -w source/ -w themes/ -w scripts/" "develop-theme":
"nodemon -x 'rm db.json; hexo serve' -w assets/ -w code/ -w source/ -w themes/ -w scripts/"
} }
} }

View file

@ -21,12 +21,12 @@ const { makeExecutableSchema } = require('graphql-tools');
const books = [ const books = [
{ {
title: "Harry Potter and the Sorcerer's stone", title: "Harry Potter and the Sorcerer's stone",
author: 'J.K. Rowling' author: 'J.K. Rowling',
}, },
{ {
title: 'Jurassic Park', title: 'Jurassic Park',
author: 'Michael Crichton' author: 'Michael Crichton',
} },
]; ];
// The GraphQL schema in string form // The GraphQL schema in string form
@ -37,13 +37,13 @@ const typeDefs = `
// The resolvers // The resolvers
const resolvers = { const resolvers = {
Query: { books: () => books } Query: { books: () => books },
}; };
// Put together a schema // Put together a schema
const schema = makeExecutableSchema({ const schema = makeExecutableSchema({
typeDefs, typeDefs,
resolvers resolvers,
}); });
// Initialize the app // Initialize the app

View file

@ -35,8 +35,8 @@ import { graphiqlExpress } from 'apollo-server-express';
app.use( app.use(
'/graphiql', '/graphiql',
graphiqlExpress({ graphiqlExpress({
endpointURL: '/graphql' endpointURL: '/graphql',
}) }),
); );
``` ```
@ -50,8 +50,8 @@ import { graphiqlConnect } from 'apollo-server-express';
app.use( app.use(
'/graphiql', '/graphiql',
graphiqlConnect({ graphiqlConnect({
endpointURL: '/graphql' endpointURL: '/graphql',
}) }),
); );
``` ```
@ -67,9 +67,9 @@ server.register({
options: { options: {
path: '/graphiql', path: '/graphiql',
graphiqlOptions: { graphiqlOptions: {
endpointURL: '/graphql' endpointURL: '/graphql',
} },
} },
}); });
``` ```

View file

@ -40,12 +40,12 @@ If you don't see your favorite server there, [file a PR](https://github.com/apol
At the end of the day, Apollo Server is a simple, production-ready solution without too many features. Here's what you can do with it: At the end of the day, Apollo Server is a simple, production-ready solution without too many features. Here's what you can do with it:
- Attach a GraphQL schema to your HTTP server to serve requests * Attach a GraphQL schema to your HTTP server to serve requests
- Attach GraphQL and GraphiQL via separate middlewares, on different routes * Attach GraphQL and GraphiQL via separate middlewares, on different routes
- Accept queries via GET or POST * Accept queries via GET or POST
- Support HTTP query batching * Support HTTP query batching
- Support Apollo Tracing to get performance information about your server * Support Apollo Tracing to get performance information about your server
- Support Apollo Cache Control to inform caching gateways such as Apollo Engine * Support Apollo Cache Control to inform caching gateways such as Apollo Engine
<h2 id="principles">Principles</h2> <h2 id="principles">Principles</h2>

View file

@ -8,9 +8,9 @@ description: How to migrate to Apollo Server 0.3 from 0.2.
Version 0.3.0 of Apollo Server contains a couple of breaking changes in the Hapi plugin API. Version 0.3.0 of Apollo Server contains a couple of breaking changes in the Hapi plugin API.
The most notable changes are: The most notable changes are:
- the plugin class has been replaced as a function to be more idiomatic * the plugin class has been replaced as a function to be more idiomatic
- the plugin name has been renamed to use camelcase * the plugin name has been renamed to use camelcase
- the options object has been extended to support additional routing options * the options object has been extended to support additional routing options
The following code snippet for Hapi Apollo 0.2.x The following code snippet for Hapi Apollo 0.2.x
@ -43,4 +43,4 @@ server.register({
}); });
``` ```
*NOTE:* That you can now pass additional routing configuration via the route options _NOTE:_ That you can now pass additional routing configuration via the route options

View file

@ -6,20 +6,20 @@ description: How to migrate from an older version of Apollo Server
Version 0.2.0 of Apollo Server contains several breaking changes in the API. Version 0.2.0 of Apollo Server contains several breaking changes in the API.
The most notable changes are: The most notable changes are:
- the `apolloServer` function no longer exists and was replaced with `apolloExpress`. * the `apolloServer` function no longer exists and was replaced with `apolloExpress`.
- `apolloExpress` no longer accepts shorthand type definitions * `apolloExpress` no longer accepts shorthand type definitions
- `apolloExpress` doesn't have the `resolvers`, `mocks` and `connectors` options. * `apolloExpress` doesn't have the `resolvers`, `mocks` and `connectors` options.
- `apolloExpress` doesn't include GraphiQL any more * `apolloExpress` doesn't include GraphiQL any more
- `context`: if you use connectors in your schema, don't forget to setup default `context` to at least an empty object, it can't be `undefined` in this case * `context`: if you use connectors in your schema, don't forget to setup default `context` to at least an empty object, it can't be `undefined` in this case
- Apollo Server no longer accepts GET requests or parameters in the URL * Apollo Server no longer accepts GET requests or parameters in the URL
- `apolloExpress` no longer parses the HTTP body automatically * `apolloExpress` no longer parses the HTTP body automatically
In order to make updating from an older version of Apollo Server easier, this guide In order to make updating from an older version of Apollo Server easier, this guide
shows how to use `graphql-tools` together with `apolloExpress` and `graphiqlExpress` to shows how to use `graphql-tools` together with `apolloExpress` and `graphiqlExpress` to
replace the old `apolloServer` function. replace the old `apolloServer` function.
The three main differences between the old and the new approach are: The three main differences between the old and the new approach are:
1. generating the schema is now done with `graphql-tools`, Apollo Server only uses the finished schema. 1. generating the schema is now done with `graphql-tools`, Apollo Server only uses the finished schema.
2. `bodyParser` has to be used to parse requests before passing them to `expressApollo` 2. `bodyParser` has to be used to parse requests before passing them to `expressApollo`
3. GraphiQL now has to be served on a separate path 3. GraphiQL now has to be served on a separate path
@ -38,22 +38,26 @@ const GRAPHQL_PORT = 8080;
const graphQLServer = express(); const graphQLServer = express();
graphQLServer.use('/graphql', apolloServer({ graphQLServer.use(
'/graphql',
apolloServer({
graphiql: true, graphiql: true,
schema: Schema, schema: Schema,
resolvers: Resolvers, resolvers: Resolvers,
connectors: Connectors, connectors: Connectors,
mocks: Mocks, mocks: Mocks,
})); }),
);
graphQLServer.listen(GRAPHQL_PORT, () => console.log( graphQLServer.listen(GRAPHQL_PORT, () =>
`Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql` console.log(
)); `Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`,
),
);
``` ```
... should be written as follows in Apollo Server 0.2.x and above: ... should be written as follows in Apollo Server 0.2.x and above:
```js ```js
import express from 'express'; import express from 'express';
@ -67,8 +71,6 @@ import { apolloExpress, graphiqlExpress } from 'apollo-server';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'; import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
const GRAPHQL_PORT = 8080; const GRAPHQL_PORT = 8080;
const graphQLServer = express(); const graphQLServer = express();
@ -86,16 +88,25 @@ addMockFunctionsToSchema({
}); });
// `context` must be an object and can't be undefined when using connectors // `context` must be an object and can't be undefined when using connectors
graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({ graphQLServer.use(
'/graphql',
bodyParser.json(),
apolloExpress({
schema: executableSchema, schema: executableSchema,
context: {}, //at least(!) an empty object context: {}, //at least(!) an empty object
})); }),
);
graphQLServer.use('/graphiql', graphiqlExpress({ graphQLServer.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql', endpointURL: '/graphql',
})); }),
);
graphQLServer.listen(GRAPHQL_PORT, () => console.log( graphQLServer.listen(GRAPHQL_PORT, () =>
`Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql` console.log(
)); `Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`,
),
);
``` ```

View file

@ -32,27 +32,21 @@ Variables can be an object or a JSON-encoded string. I.e. the following is equiv
A batch of queries can be sent by simply sending a JSON-encoded array of queries, e.g. A batch of queries can be sent by simply sending a JSON-encoded array of queries, e.g.
```js ```js
[ [{ query: '{ testString }' }, { query: 'query q2{ test(who: "you" ) }' }];
{ "query": "{ testString }" },
{ "query": "query q2{ test(who: \"you\" ) }" }
]
``` ```
If a batch of queries is sent, the response will be an array of GraphQL responses. If a batch of queries is sent, the response will be an array of GraphQL responses.
If Apollo Server is running under a different origin than your client, you will need to enable CORS support on the server, or proxy the GraphQL requests through a web server under the main origin. If Apollo Server is running under a different origin than your client, you will need to enable CORS support on the server, or proxy the GraphQL requests through a web server under the main origin.
<h2 id="getRequests">GET requests</h2> <h2 id="getRequests">GET requests</h2>
Apollo Server also accepts GET requests. A GET request must pass query and optionally variables and operationName in the URL. Apollo Server also accepts GET requests. A GET request must pass query and optionally variables and operationName in the URL.
Here is the same query from above in a well-formatted GET request to Apollo Server: Here is the same query from above in a well-formatted GET request to Apollo Server:
``` ```
GET /graphql?query=query%20aTest(%24arg1%3A%20String!)%20%7B%20test(who%3A%20%24arg1)%20%7D&operationName=aTest&variables=me GET /graphql?query=query%20aTest(%24arg1%3A%20String!)%20%7B%20test(who%3A%20%24arg1)%20%7D&operationName=aTest&variables=me
``` ```
caveat: Mutations cannot be executed via GET requests. caveat: Mutations cannot be executed via GET requests.

View file

@ -16,9 +16,9 @@ app.use(
'/graphql', '/graphql',
bodyParser.json(), bodyParser.json(),
graphqlExpress({ graphqlExpress({
schema: myGraphQLSchema schema: myGraphQLSchema,
// other options here // other options here
}) }),
); );
``` ```
@ -34,11 +34,11 @@ app.use(
return { return {
schema: myGraphQLSchema, schema: myGraphQLSchema,
context: { context: {
value: req.body.something value: req.body.something,
} },
// other options here // other options here
}; };
}) }),
); );
``` ```
@ -67,11 +67,11 @@ app.use(
return { return {
schema: myGraphQLSchema, schema: myGraphQLSchema,
context: { context: {
user: userForThisRequest user: userForThisRequest,
} },
// other options here // other options here
}; };
}) }),
); );
``` ```
@ -126,8 +126,8 @@ const GraphQLOptions = {
To see how to use the middleware with your particular JavaScript server, check out the docs for those: To see how to use the middleware with your particular JavaScript server, check out the docs for those:
- [Express / Connect](./servers/express.html) * [Express / Connect](./servers/express.html)
- [Hapi](./servers/hapi.html) * [Hapi](./servers/hapi.html)
- [Koa](./servers/koa.html) * [Koa](./servers/koa.html)
And more are being added every day! And more are being added every day!

View file

@ -12,7 +12,5 @@
"tag: internal": ":house: Internal" "tag: internal": ":house: Internal"
} }
}, },
"packages": [ "packages": ["packages/*"]
"packages/*"
]
} }

View file

@ -7,19 +7,33 @@
}, },
"scripts": { "scripts": {
"compile": "lerna exec -- npm run compile", "compile": "lerna exec -- npm run compile",
"lint": "tslint ./packages/**/src/**/*.ts --exclude \"./packages/**/node_modules/**/*.ts\"", "lint":
"prettier-check --ignore-path .gitignore \"{docs/{,source/**},.,packages/**,test}/{*.{j,t}s*,*.md,*.json}\"",
"lint-fix":
"prettier --write --ignore-path .gitignore \"{docs/{,source/**},.,packages/**,test}/{*.{j,t}s*,*.md,*.json}\"",
"prebootstrap": "npm install", "prebootstrap": "npm install",
"postinstall": "lerna bootstrap", "postinstall": "lerna bootstrap",
"pretest": "npm run compile", "pretest": "npm run compile",
"test": "npm run testonly --", "test": "npm run testonly --",
"posttest": "npm run lint", "posttest": "npm run lint",
"testonly": "mocha --reporter spec --full-trace --timeout 5000 ./test/tests.js", "testonly":
"coverage": "istanbul cover -x \"*.test.js\" _mocha -- --timeout 5000 --full-trace --reporter dot ./test/tests.js", "mocha --reporter spec --full-trace --timeout 5000 ./test/tests.js",
"coverage":
"istanbul cover -x \"*.test.js\" _mocha -- --timeout 5000 --full-trace --reporter dot ./test/tests.js",
"pretravis": "npm run compile", "pretravis": "npm run compile",
"travis": "istanbul cover -x \"*.test.js\" _mocha -- --timeout 5000 --full-trace ./test/tests.js", "travis":
"istanbul cover -x \"*.test.js\" _mocha -- --timeout 5000 --full-trace ./test/tests.js",
"posttravis": "npm run lint", "posttravis": "npm run lint",
"postcoverage": "remap-istanbul --input coverage/coverage.raw.json --type lcovonly --output coverage/lcov.info", "postcoverage":
"release": "lerna publish" "remap-istanbul --input coverage/coverage.raw.json --type lcovonly --output coverage/lcov.info",
"release": "lerna publish",
"precommit": "lint-staged"
},
"lint-staged": {
"*.ts*": ["prettier --write", "git add"],
"*.js*": ["prettier --write", "git add"],
"*.json*": ["prettier --write", "git add"],
"*.md*": ["prettier --write", "git add"]
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "4.1.0", "@types/chai": "4.1.0",
@ -28,15 +42,18 @@
"@types/sinon": "4.1.2", "@types/sinon": "4.1.2",
"chai": "4.1.2", "chai": "4.1.2",
"graphql": "0.11.7", "graphql": "0.11.7",
"husky": "^0.14.3",
"istanbul": "1.1.0-alpha.1", "istanbul": "1.1.0-alpha.1",
"lerna": "2.5.1", "lerna": "2.5.1",
"lint-staged": "6.0.0",
"mocha": "4.1.0", "mocha": "4.1.0",
"npm-check-updates": "2.14.0", "npm-check-updates": "2.14.0",
"prettier": "^1.9.2",
"prettier-check": "^2.0.0",
"remap-istanbul": "0.9.5", "remap-istanbul": "0.9.5",
"sinon": "4.1.4", "sinon": "4.1.4",
"supertest": "3.0.0", "supertest": "3.0.0",
"supertest-as-promised": "4.0.2", "supertest-as-promised": "4.0.2",
"tslint": "5.7.0",
"typescript": "2.6.2" "typescript": "2.6.2"
} }
} }

View file

@ -41,8 +41,8 @@ const Route = use('Route');
Route.get( Route.get(
'/graphiql', '/graphiql',
graphiqlAdonis({ graphiqlAdonis({
endpointURL: '/graphql' // a POST endpoint that GraphiQL will make the actual requests to endpointURL: '/graphql', // a POST endpoint that GraphiQL will make the actual requests to
}) }),
); );
``` ```
@ -62,8 +62,8 @@ Route.get(
'/graphiql', '/graphiql',
graphiqlAdonis({ graphiqlAdonis({
endpointURL: '/graphql', endpointURL: '/graphql',
passHeader: `'Authorization': 'Bearer lorem ipsum'` passHeader: `'Authorization': 'Bearer lorem ipsum'`,
}) }),
); );
``` ```

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-adonis" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-adonis"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Adonis", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Adonis",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,7 +4,10 @@ import { setupResolver, Config } from '@adonisjs/sink';
import { graphqlAdonis, graphiqlAdonis } from './adonisApollo'; import { graphqlAdonis, graphiqlAdonis } from './adonisApollo';
import { GraphQLOptions } from 'apollo-server-core'; import { GraphQLOptions } from 'apollo-server-core';
import { expect } from 'chai'; import { expect } from 'chai';
import testSuite, { schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
const RouteStore = require('@adonisjs/framework/src/Route/Store'); const RouteStore = require('@adonisjs/framework/src/Route/Store');
@ -12,16 +15,12 @@ async function createApp(options: CreateAppOptions = {}) {
ioc.restore(); ioc.restore();
RouteStore.clear(); RouteStore.clear();
options.graphqlOptions = options.graphqlOptions || { schema }; options.graphqlOptions = options.graphqlOptions || { schema };
const providers = [ const providers = ['@adonisjs/framework/providers/AppProvider'];
'@adonisjs/framework/providers/AppProvider',
];
if (!options.excludeParser) { if (!options.excludeParser) {
providers.push('@adonisjs/bodyparser/providers/BodyParserProvider'); providers.push('@adonisjs/bodyparser/providers/BodyParserProvider');
} }
setupResolver(); setupResolver();
registrar registrar.providers(providers).register();
.providers(providers)
.register();
ioc.bind('Adonis/Src/Config', () => { ioc.bind('Adonis/Src/Config', () => {
const config = new Config(); const config = new Config();
config.set('app', { config.set('app', {
@ -41,13 +40,21 @@ async function createApp(options: CreateAppOptions = {}) {
const Route = ioc.use('Adonis/Src/Route'); const Route = ioc.use('Adonis/Src/Route');
const Server = ioc.use('Adonis/Src/Server'); const Server = ioc.use('Adonis/Src/Server');
Context.getter('request', function () { Context.getter(
'request',
function() {
return new Request(this.req, this.res, ioc.use('Adonis/Src/Config')); return new Request(this.req, this.res, ioc.use('Adonis/Src/Config'));
}, true); },
true,
);
Context.getter('response', function () { Context.getter(
'response',
function() {
return new Response(this.req, this.res, ioc.use('Adonis/Src/Config')); return new Response(this.req, this.res, ioc.use('Adonis/Src/Config'));
}, true); },
true,
);
Route.post('/graphql', graphqlAdonis(options.graphqlOptions)); Route.post('/graphql', graphqlAdonis(options.graphqlOptions));
Route.get('/graphql', graphqlAdonis(options.graphqlOptions)); Route.get('/graphql', graphqlAdonis(options.graphqlOptions));
@ -57,7 +64,7 @@ async function createApp(options: CreateAppOptions = {}) {
if (!options.excludeParser) { if (!options.excludeParser) {
Server.registerGlobal(['Adonis/Middleware/BodyParser']); Server.registerGlobal(['Adonis/Middleware/BodyParser']);
} }
await new Promise((resolve) => Server.listen('localhost', 3333, resolve)); await new Promise(resolve => Server.listen('localhost', 3333, resolve));
return Server.getInstance(); return Server.getInstance();
} }
@ -65,17 +72,20 @@ async function destroyApp(app) {
if (!app || !app.close) { if (!app || !app.close) {
return; return;
} }
await new Promise((resolve) => app.close(resolve)); await new Promise(resolve => app.close(resolve));
} }
describe('adonisApollo', () => { describe('adonisApollo', () => {
it('throws error if called without schema', function() { it('throws error if called without schema', function() {
expect(() => graphqlAdonis(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.'); expect(() => graphqlAdonis(undefined as GraphQLOptions)).to.throw(
'Apollo Server requires options.',
);
}); });
it('throws an error if called with more than one argument', function() { it('throws an error if called with more than one argument', function() {
expect(() => (<any>graphqlAdonis)({}, 'x')).to.throw( expect(() => (<any>graphqlAdonis)({}, 'x')).to.throw(
'Apollo Server expects exactly one argument, got 2'); 'Apollo Server expects exactly one argument, got 2',
);
}); });
}); });

View file

@ -1,5 +1,9 @@
import AdonisContext from '@adonisjs/framework/src/Context'; import AdonisContext from '@adonisjs/framework/src/Context';
import { GraphQLOptions, HttpQueryError, runHttpQuery } from 'apollo-server-core'; import {
GraphQLOptions,
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
export interface AdonisGraphQLOptionsFunction { export interface AdonisGraphQLOptionsFunction {
@ -10,32 +14,41 @@ export interface AdonisHandler {
(req: any, next): void; (req: any, next): void;
} }
export function graphqlAdonis (options: GraphQLOptions | AdonisGraphQLOptionsFunction): AdonisHandler { export function graphqlAdonis(
options: GraphQLOptions | AdonisGraphQLOptionsFunction,
): AdonisHandler {
if (!options) { if (!options) {
throw new Error('Apollo Server requires options.'); throw new Error('Apollo Server requires options.');
} }
if (arguments.length > 1) { if (arguments.length > 1) {
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`); throw new Error(
`Apollo Server expects exactly one argument, got ${arguments.length}`,
);
} }
return (ctx: AdonisContext): Promise<void> => { return (ctx: AdonisContext): Promise<void> => {
const { request, response } = ctx; const { request, response } = ctx;
const method = request.method(); const method = request.method();
const query = method === 'POST' ? request.post() : request.get(); const query = method === 'POST' ? request.post() : request.get();
return runHttpQuery([ctx], { return runHttpQuery([ctx], {
method, options, query, method,
}).then(gqlResponse => { options,
query,
}).then(
gqlResponse => {
response.json(gqlResponse); response.json(gqlResponse);
}, (error: HttpQueryError) => { },
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) { if ('HttpQueryError' !== error.name) {
throw error; throw error;
} }
if (error.headers) { if (error.headers) {
Object.keys(error.headers).forEach((header) => { Object.keys(error.headers).forEach(header => {
response.header(header, error.headers[header]); response.header(header, error.headers[header]);
}); });
} }
response.status(error.statusCode).send(error.message); response.status(error.statusCode).send(error.message);
}); },
);
}; };
} }
@ -43,15 +56,19 @@ export interface AdonisGraphiQLOptionsFunction {
(ctx: AdonisContext): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>; (ctx: AdonisContext): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
} }
export function graphiqlAdonis (options: GraphiQL.GraphiQLData | AdonisGraphiQLOptionsFunction) { export function graphiqlAdonis(
options: GraphiQL.GraphiQLData | AdonisGraphiQLOptionsFunction,
) {
return (ctx: AdonisContext): Promise<void> => { return (ctx: AdonisContext): Promise<void> => {
const { request, response } = ctx; const { request, response } = ctx;
const query = request.get(); const query = request.get();
return GraphiQL.resolveGraphiQLString(query, options, ctx) return GraphiQL.resolveGraphiQLString(query, options, ctx).then(
.then(graphiqlString => { graphiqlString => {
response.type('text/html').send(graphiqlString); response.type('text/html').send(graphiqlString);
}, (error: HttpQueryError) => { },
(error: HttpQueryError) => {
response.status(500).send(error.message); response.status(500).send(error.message);
}); },
);
}; };
} }

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -7,12 +7,11 @@ description: Setting up Apollo Server with Azure Functions
This is the Azure Functions integration for the Apollo community GraphQL Server. [Read the docs.](https://www.apollographql.com/docs/apollo-server/) This is the Azure Functions integration for the Apollo community GraphQL Server. [Read the docs.](https://www.apollographql.com/docs/apollo-server/)
## Example: ## Example:
```js ```js
const server = require("apollo-server-azure-functions"); const server = require('apollo-server-azure-functions');
const graphqlTools = require("graphql-tools"); const graphqlTools = require('graphql-tools');
const typeDefs = ` const typeDefs = `
type Random { type Random {
@ -26,28 +25,28 @@ const typeDefs = `
} }
`; `;
const rands = [{ id: 1, rand: "random" }, { id: 2, rand: "modnar" }]; const rands = [{ id: 1, rand: 'random' }, { id: 2, rand: 'modnar' }];
const resolvers = { const resolvers = {
Query: { Query: {
rands: () => rands, rands: () => rands,
rand: (_, { id }) => rands.find(rand => rand.id === id) rand: (_, { id }) => rands.find(rand => rand.id === id),
} },
}; };
const schema = graphqlTools.makeExecutableSchema({ const schema = graphqlTools.makeExecutableSchema({
typeDefs, typeDefs,
resolvers resolvers,
}); });
module.exports = function run(context, request) { module.exports = function run(context, request) {
if (request.method === "POST") { if (request.method === 'POST') {
server.graphqlAzureFunctions({ server.graphqlAzureFunctions({
endpointURL: '/api/graphql' endpointURL: '/api/graphql',
})(context, request); })(context, request);
} else if (request.method === "GET") { } else if (request.method === 'GET') {
return server.graphiqlAzureFunctions({ return server.graphiqlAzureFunctions({
endpointURL: '/api/graphql' endpointURL: '/api/graphql',
})(context, request); })(context, request);
} }
}; };

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-azure-functions" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-azure-functions"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Azure", "Functions"],
"GraphQL",
"Apollo",
"Server",
"Azure",
"Functions"
],
"author": "Ulrik Strid <ulrik.strid@outlook.com>", "author": "Ulrik Strid <ulrik.strid@outlook.com>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -23,7 +23,7 @@ export interface IHeaders {
'content-range'?: string; 'content-range'?: string;
'content-location'?: string; 'content-location'?: string;
'content-md5'?: Buffer; 'content-md5'?: Buffer;
'expires'?: Date; expires?: Date;
'last-modified'?: Date; 'last-modified'?: Date;
[header: string]: any; [header: string]: any;
} }

View file

@ -3,15 +3,8 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"],
"node_modules/@types" "types": ["@types/node"]
],
"types": [
"@types/node"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -10,14 +10,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-core" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-core"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -1,4 +1,8 @@
import { GraphQLSchema, ValidationContext, GraphQLFieldResolver } from 'graphql'; import {
GraphQLSchema,
ValidationContext,
GraphQLFieldResolver,
} from 'graphql';
import { LogFunction } from './runQuery'; import { LogFunction } from './runQuery';
import { GraphQLExtension } from 'graphql-extensions'; import { GraphQLExtension } from 'graphql-extensions';
@ -34,7 +38,10 @@ export interface GraphQLServerOptions {
export default GraphQLServerOptions; export default GraphQLServerOptions;
export async function resolveGraphqlOptions(options: GraphQLServerOptions | Function, ...args): Promise<GraphQLServerOptions> { export async function resolveGraphqlOptions(
options: GraphQLServerOptions | Function,
...args
): Promise<GraphQLServerOptions> {
if (isOptionsFunction(options)) { if (isOptionsFunction(options)) {
try { try {
return await options(...args); return await options(...args);
@ -46,6 +53,8 @@ export async function resolveGraphqlOptions(options: GraphQLServerOptions | Func
} }
} }
export function isOptionsFunction(arg: GraphQLServerOptions | Function): arg is Function { export function isOptionsFunction(
arg: GraphQLServerOptions | Function,
): arg is Function {
return typeof arg === 'function'; return typeof arg === 'function';
} }

View file

@ -1,4 +1,12 @@
export { runQuery, LogFunction, LogMessage, LogStep, LogAction } from './runQuery'; export {
runQuery,
LogFunction,
LogMessage,
LogStep,
LogAction,
} from './runQuery';
export { runHttpQuery, HttpQueryRequest, HttpQueryError } from './runHttpQuery'; export { runHttpQuery, HttpQueryRequest, HttpQueryError } from './runHttpQuery';
export { default as GraphQLOptions, resolveGraphqlOptions } from './graphqlOptions'; export {
default as GraphQLOptions,
resolveGraphqlOptions,
} from './graphqlOptions';

View file

@ -1,6 +1,15 @@
import { parse, getOperationAST, DocumentNode, formatError, ExecutionResult } from 'graphql'; import {
parse,
getOperationAST,
DocumentNode,
formatError,
ExecutionResult,
} from 'graphql';
import { runQuery } from './runQuery'; import { runQuery } from './runQuery';
import { default as GraphQLOptions, resolveGraphqlOptions } from './graphqlOptions'; import {
default as GraphQLOptions,
resolveGraphqlOptions,
} from './graphqlOptions';
export interface HttpQueryRequest { export interface HttpQueryRequest {
method: string; method: string;
@ -13,7 +22,12 @@ export class HttpQueryError extends Error {
public isGraphQLError: boolean; public isGraphQLError: boolean;
public headers: { [key: string]: string }; public headers: { [key: string]: string };
constructor (statusCode: number, message: string, isGraphQLError: boolean = false, headers?: { [key: string]: string }) { constructor(
statusCode: number,
message: string,
isGraphQLError: boolean = false,
headers?: { [key: string]: string },
) {
super(message); super(message);
this.name = 'HttpQueryError'; this.name = 'HttpQueryError';
this.statusCode = statusCode; this.statusCode = statusCode;
@ -31,12 +45,18 @@ function isFunction(arg: any): arg is Function {
return typeof arg === 'function'; return typeof arg === 'function';
} }
export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQueryRequest): Promise<string> { export async function runHttpQuery(
handlerArguments: Array<any>,
request: HttpQueryRequest,
): Promise<string> {
let isGetRequest: boolean = false; let isGetRequest: boolean = false;
let optionsObject: GraphQLOptions; let optionsObject: GraphQLOptions;
try { try {
optionsObject = await resolveGraphqlOptions(request.options, ...handlerArguments); optionsObject = await resolveGraphqlOptions(
request.options,
...handlerArguments,
);
} catch (e) { } catch (e) {
throw new HttpQueryError(500, e.message); throw new HttpQueryError(500, e.message);
} }
@ -45,14 +65,17 @@ export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQu
switch (request.method) { switch (request.method) {
case 'POST': case 'POST':
if ( !request.query || (Object.keys(request.query).length === 0) ) { if (!request.query || Object.keys(request.query).length === 0) {
throw new HttpQueryError(500, 'POST body missing. Did you forget use body-parser middleware?'); throw new HttpQueryError(
500,
'POST body missing. Did you forget use body-parser middleware?',
);
} }
requestPayload = request.query; requestPayload = request.query;
break; break;
case 'GET': case 'GET':
if ( !request.query || (Object.keys(request.query).length === 0) ) { if (!request.query || Object.keys(request.query).length === 0) {
throw new HttpQueryError(400, 'GET query missing.'); throw new HttpQueryError(400, 'GET query missing.');
} }
@ -61,9 +84,14 @@ export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQu
break; break;
default: default:
throw new HttpQueryError(405, 'Apollo Server supports only GET/POST requests.', false, { throw new HttpQueryError(
'Allow': 'GET, POST', 405,
}); 'Apollo Server supports only GET/POST requests.',
false,
{
Allow: 'GET, POST',
},
);
} }
let isBatch = true; let isBatch = true;
@ -84,9 +112,14 @@ export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQu
} }
if (!isQueryOperation(query, requestParams.operationName)) { if (!isQueryOperation(query, requestParams.operationName)) {
throw new HttpQueryError(405, `GET supports only query operation`, false, { throw new HttpQueryError(
'Allow': 'POST', 405,
}); `GET supports only query operation`,
false,
{
Allow: 'POST',
},
);
} }
} }
@ -105,7 +138,10 @@ export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQu
if (isFunction(context)) { if (isFunction(context)) {
context = context(); context = context();
} else if (isBatch) { } else if (isBatch) {
context = Object.assign(Object.create(Object.getPrototypeOf(context)), context); context = Object.assign(
Object.create(Object.getPrototypeOf(context)),
context,
);
} }
let params = { let params = {

View file

@ -12,11 +12,7 @@ import {
parse, parse,
} from 'graphql'; } from 'graphql';
import { import { runQuery, LogAction, LogStep } from './runQuery';
runQuery,
LogAction,
LogStep,
} from './runQuery';
// Make the global Promise constructor Fiber-aware to simulate a Meteor // Make the global Promise constructor Fiber-aware to simulate a Meteor
// environment. // environment.
@ -95,8 +91,7 @@ describe('runQuery', () => {
it('returns the right result when query is a string', () => { it('returns the right result when query is a string', () => {
const query = `{ testString }`; const query = `{ testString }`;
const expected = { testString: 'it works' }; const expected = { testString: 'it works' };
return runQuery({ schema, query: query }) return runQuery({ schema, query: query }).then(res => {
.then((res) => {
return expect(res.data).to.deep.equal(expected); return expect(res.data).to.deep.equal(expected);
}); });
}); });
@ -104,8 +99,7 @@ describe('runQuery', () => {
it('returns the right result when query is a document', () => { it('returns the right result when query is a document', () => {
const query = parse(`{ testString }`); const query = parse(`{ testString }`);
const expected = { testString: 'it works' }; const expected = { testString: 'it works' };
return runQuery({ schema, query: query }) return runQuery({ schema, query: query }).then(res => {
.then((res) => {
return expect(res.data).to.deep.equal(expected); return expect(res.data).to.deep.equal(expected);
}); });
}); });
@ -117,7 +111,7 @@ describe('runQuery', () => {
schema, schema,
query: query, query: query,
variables: { base: 1 }, variables: { base: 1 },
}).then((res) => { }).then(res => {
expect(res.data).to.be.undefined; expect(res.data).to.be.undefined;
expect(res.errors.length).to.equal(1); expect(res.errors.length).to.equal(1);
return expect(res.errors[0].message).to.match(expected); return expect(res.errors[0].message).to.match(expected);
@ -132,7 +126,7 @@ describe('runQuery', () => {
schema, schema,
query: query, query: query,
debug: true, debug: true,
}).then((res) => { }).then(res => {
logStub.restore(); logStub.restore();
expect(logStub.callCount).to.equal(1); expect(logStub.callCount).to.equal(1);
return expect(logStub.getCall(0).args[0]).to.match(expected); return expect(logStub.getCall(0).args[0]).to.match(expected);
@ -146,7 +140,7 @@ describe('runQuery', () => {
schema, schema,
query: query, query: query,
debug: false, debug: false,
}).then((res) => { }).then(res => {
logStub.restore(); logStub.restore();
return expect(logStub.callCount).to.equal(0); return expect(logStub.callCount).to.equal(0);
}); });
@ -154,12 +148,13 @@ describe('runQuery', () => {
it('returns a validation error if the query string does not pass validation', () => { it('returns a validation error if the query string does not pass validation', () => {
const query = `query TestVar($base: String){ testArgumentValue(base: $base) }`; const query = `query TestVar($base: String){ testArgumentValue(base: $base) }`;
const expected = 'Variable "$base" of type "String" used in position expecting type "Int!".'; const expected =
'Variable "$base" of type "String" used in position expecting type "Int!".';
return runQuery({ return runQuery({
schema, schema,
query: query, query: query,
variables: { base: 1 }, variables: { base: 1 },
}).then((res) => { }).then(res => {
expect(res.data).to.be.undefined; expect(res.data).to.be.undefined;
expect(res.errors.length).to.equal(1); expect(res.errors.length).to.equal(1);
return expect(res.errors[0].message).to.deep.equal(expected); return expect(res.errors[0].message).to.deep.equal(expected);
@ -169,17 +164,17 @@ describe('runQuery', () => {
it('correctly passes in the rootValue', () => { it('correctly passes in the rootValue', () => {
const query = `{ testRootValue }`; const query = `{ testRootValue }`;
const expected = { testRootValue: 'it also works' }; const expected = { testRootValue: 'it also works' };
return runQuery({ schema, query: query, rootValue: 'it also' }) return runQuery({ schema, query: query, rootValue: 'it also' }).then(
.then((res) => { res => {
return expect(res.data).to.deep.equal(expected); return expect(res.data).to.deep.equal(expected);
}); },
);
}); });
it('correctly passes in the context', () => { it('correctly passes in the context', () => {
const query = `{ testContextValue }`; const query = `{ testContextValue }`;
const expected = { testContextValue: 'it still works' }; const expected = { testContextValue: 'it still works' };
return runQuery({ schema, query: query, context: 'it still' }) return runQuery({ schema, query: query, context: 'it still' }).then(res => {
.then((res) => {
return expect(res.data).to.deep.equal(expected); return expect(res.data).to.deep.equal(expected);
}); });
}); });
@ -195,8 +190,7 @@ describe('runQuery', () => {
response['extensions'] = context; response['extensions'] = context;
return response; return response;
}, },
}) }).then(res => {
.then((res) => {
expect(res.data).to.deep.equal(expected); expect(res.data).to.deep.equal(expected);
return expect(res['extensions']).to.equal('it still'); return expect(res['extensions']).to.equal('it still');
}); });
@ -209,18 +203,19 @@ describe('runQuery', () => {
schema, schema,
query: query, query: query,
variables: { base: 1 }, variables: { base: 1 },
}).then((res) => { }).then(res => {
return expect(res.data).to.deep.equal(expected); return expect(res.data).to.deep.equal(expected);
}); });
}); });
it('throws an error if there are missing variables', () => { it('throws an error if there are missing variables', () => {
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`; const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
const expected = 'Variable "$base" of required type "Int!" was not provided.'; const expected =
'Variable "$base" of required type "Int!" was not provided.';
return runQuery({ return runQuery({
schema, schema,
query: query, query: query,
}).then((res) => { }).then(res => {
return expect(res.errors[0].message).to.deep.equal(expected); return expect(res.errors[0].message).to.deep.equal(expected);
}); });
}); });
@ -229,7 +224,7 @@ describe('runQuery', () => {
return runQuery({ return runQuery({
schema, schema,
query: `{ testAwaitedValue }`, query: `{ testAwaitedValue }`,
}).then((res) => { }).then(res => {
expect(res.data).to.deep.equal({ expect(res.data).to.deep.equal({
testAwaitedValue: 'it works', testAwaitedValue: 'it works',
}); });
@ -247,8 +242,7 @@ describe('runQuery', () => {
const expected = { const expected = {
testString: 'it works', testString: 'it works',
}; };
return runQuery({ schema, query: query, operationName: 'Q1' }) return runQuery({ schema, query: query, operationName: 'Q1' }).then(res => {
.then((res) => {
return expect(res.data).to.deep.equal(expected); return expect(res.data).to.deep.equal(expected);
}); });
}); });
@ -259,7 +253,7 @@ describe('runQuery', () => {
testString testString
}`; }`;
const logs = []; const logs = [];
const logFn = (obj) => logs.push(obj); const logFn = obj => logs.push(obj);
const expected = { const expected = {
testString: 'it works', testString: 'it works',
}; };
@ -269,15 +263,35 @@ describe('runQuery', () => {
operationName: 'Q1', operationName: 'Q1',
variables: { test: 123 }, variables: { test: 123 },
logFunction: logFn, logFunction: logFn,
}) }).then(res => {
.then((res) => {
expect(res.data).to.deep.equal(expected); expect(res.data).to.deep.equal(expected);
expect(logs.length).to.equals(11); expect(logs.length).to.equals(11);
expect(logs[0]).to.deep.equals({action: LogAction.request, step: LogStep.start}); expect(logs[0]).to.deep.equals({
expect(logs[1]).to.deep.equals({action: LogAction.request, step: LogStep.status, key: 'query', data: query}); action: LogAction.request,
expect(logs[2]).to.deep.equals({action: LogAction.request, step: LogStep.status, key: 'variables', data: { test: 123 }}); step: LogStep.start,
expect(logs[3]).to.deep.equals({action: LogAction.request, step: LogStep.status, key: 'operationName', data: 'Q1'}); });
expect(logs[10]).to.deep.equals({action: LogAction.request, step: LogStep.end}); expect(logs[1]).to.deep.equals({
action: LogAction.request,
step: LogStep.status,
key: 'query',
data: query,
});
expect(logs[2]).to.deep.equals({
action: LogAction.request,
step: LogStep.status,
key: 'variables',
data: { test: 123 },
});
expect(logs[3]).to.deep.equals({
action: LogAction.request,
step: LogStep.status,
key: 'operationName',
data: 'Q1',
});
expect(logs[10]).to.deep.equals({
action: LogAction.request,
step: LogStep.end,
});
}); });
}); });
@ -328,7 +342,7 @@ describe('runQuery', () => {
} }
before(() => { before(() => {
asyncHook = asyncHooks.createHook({ init: (asyncId) => ids.push(asyncId) }); asyncHook = asyncHooks.createHook({ init: asyncId => ids.push(asyncId) });
asyncHook.enable(); asyncHook.enable();
}); });

View file

@ -13,7 +13,11 @@ import {
ValidationContext, ValidationContext,
} from 'graphql'; } from 'graphql';
import { enableGraphQLExtensions, GraphQLExtension, GraphQLExtensionStack } from 'graphql-extensions'; import {
enableGraphQLExtensions,
GraphQLExtension,
GraphQLExtensionStack,
} from 'graphql-extensions';
import { TracingExtension } from 'apollo-tracing'; import { TracingExtension } from 'apollo-tracing';
import { CacheControlExtension } from 'apollo-cache-control'; import { CacheControlExtension } from 'apollo-cache-control';
@ -24,11 +28,16 @@ export interface GraphQLResponse {
} }
export enum LogAction { export enum LogAction {
request, parse, validation, execute, request,
parse,
validation,
execute,
} }
export enum LogStep { export enum LogStep {
start, end, status, start,
end,
status,
} }
export interface LogMessage { export interface LogMessage {
@ -70,9 +79,15 @@ function runQuery(options: QueryOptions): Promise<GraphQLResponse> {
function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> { function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
let documentAST: DocumentNode; let documentAST: DocumentNode;
const logFunction = options.logFunction || function(){ return null; }; const logFunction =
const debugDefault = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'; options.logFunction ||
const debug = typeof options.debug !== 'undefined' ? options.debug : debugDefault; function() {
return null;
};
const debugDefault =
process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
const debug =
typeof options.debug !== 'undefined' ? options.debug : debugDefault;
logFunction({ action: LogAction.request, step: LogStep.start }); logFunction({ action: LogAction.request, step: LogStep.start });
@ -84,7 +99,8 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
if (options.cacheControl) { if (options.cacheControl) {
extensions.push(CacheControlExtension); extensions.push(CacheControlExtension);
} }
const extensionStack = extensions.length > 0 && new GraphQLExtensionStack(extensions); const extensionStack =
extensions.length > 0 && new GraphQLExtensionStack(extensions);
if (extensionStack) { if (extensionStack) {
context._extensionStack = extensionStack; context._extensionStack = extensionStack;
@ -94,7 +110,7 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
} }
function format(errors: Array<Error>): Array<Error> { function format(errors: Array<Error>): Array<Error> {
return errors.map((error) => { return errors.map(error => {
if (options.formatError) { if (options.formatError) {
try { try {
return options.formatError(error); return options.formatError(error);
@ -113,10 +129,26 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
console.error(error.stack); console.error(error.stack);
} }
const qry = typeof options.query === 'string' ? options.query : print(options.query); const qry =
logFunction({action: LogAction.request, step: LogStep.status, key: 'query', data: qry}); typeof options.query === 'string' ? options.query : print(options.query);
logFunction({action: LogAction.request, step: LogStep.status, key: 'variables', data: options.variables}); logFunction({
logFunction({action: LogAction.request, step: LogStep.status, key: 'operationName', data: options.operationName}); action: LogAction.request,
step: LogStep.status,
key: 'query',
data: qry,
});
logFunction({
action: LogAction.request,
step: LogStep.status,
key: 'variables',
data: options.variables,
});
logFunction({
action: LogAction.request,
step: LogStep.status,
key: 'operationName',
data: options.operationName,
});
// if query is already an AST, don't parse or validate // if query is already an AST, don't parse or validate
// XXX: This refers the operations-store flow. // XXX: This refers the operations-store flow.
@ -150,7 +182,8 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
try { try {
logFunction({ action: LogAction.execute, step: LogStep.start }); logFunction({ action: LogAction.execute, step: LogStep.start });
return Promise.resolve(execute( return Promise.resolve(
execute(
options.schema, options.schema,
documentAST, documentAST,
options.rootValue, options.rootValue,
@ -158,7 +191,8 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
options.variables, options.variables,
options.operationName, options.operationName,
options.fieldResolver, options.fieldResolver,
)).then(result => { ),
).then(result => {
logFunction({ action: LogAction.execute, step: LogStep.end }); logFunction({ action: LogAction.execute, step: LogStep.end });
logFunction({ action: LogAction.request, step: LogStep.end }); logFunction({ action: LogAction.request, step: LogStep.end });

View file

@ -4,11 +4,6 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"include" : [ "include": ["src/**/*"],
"src/**/*" "exclude": ["node_modules", "dist"]
],
"exclude": [
"node_modules",
"dist"
]
} }

View file

@ -56,5 +56,4 @@ GraphQL Server is built with the following principles in mind:
* **Simplicity**: by keeping things simple, GraphQL Server is easier to use, easier to contribute to, and more secure * **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 * **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! 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!

View file

@ -1,7 +1,8 @@
{ {
"name": "apollo-server-express", "name": "apollo-server-express",
"version": "1.3.1", "version": "1.3.1",
"description": "Production-ready Node.js GraphQL server for Express and Connect", "description":
"Production-ready Node.js GraphQL server for Express and Connect",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"compile": "tsc", "compile": "tsc",
@ -9,7 +10,8 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express"
}, },
"keywords": [ "keywords": [
"GraphQL", "GraphQL",

View file

@ -36,7 +36,7 @@ import {
GraphQLString, GraphQLString,
GraphQLScalarType, GraphQLScalarType,
GraphQLError, GraphQLError,
BREAK BREAK,
} from 'graphql'; } from 'graphql';
const QueryRootType = new GraphQLObjectType({ const QueryRootType = new GraphQLObjectType({
@ -46,14 +46,16 @@ const QueryRootType = new GraphQLObjectType({
type: GraphQLString, type: GraphQLString,
args: { args: {
who: { who: {
type: GraphQLString type: GraphQLString,
}
}, },
resolve: (root, args) => 'Hello ' + (args['who'] || 'World') },
resolve: (root, args) => 'Hello ' + (args['who'] || 'World'),
}, },
thrower: { thrower: {
type: new GraphQLNonNull(GraphQLString), type: new GraphQLNonNull(GraphQLString),
resolve: () => { throw new Error('Throws!'); } resolve: () => {
throw new Error('Throws!');
},
}, },
custom: { custom: {
type: GraphQLString, type: GraphQLString,
@ -69,14 +71,14 @@ const QueryRootType = new GraphQLObjectType({
throw new Error('Something bad happened'); throw new Error('Something bad happened');
}, },
}), }),
} },
} },
}, },
context: { context: {
type: GraphQLString, type: GraphQLString,
resolve: (obj, args, context) => context, resolve: (obj, args, context) => context,
} },
} },
}); });
const TestSchema = new GraphQLSchema({ const TestSchema = new GraphQLSchema({
@ -86,15 +88,15 @@ const TestSchema = new GraphQLSchema({
fields: { fields: {
writeTest: { writeTest: {
type: QueryRootType, type: QueryRootType,
resolve: () => ({}) resolve: () => ({}),
} },
} },
}) }),
}); });
function catchError(p) { function catchError(p) {
return p.then( return p.then(
(res) => { res => {
// workaround for unknown issues with testing against npm package of express-graphql. // workaround for unknown issues with testing against npm package of express-graphql.
// the same code works when testing against the source, I'm not sure why. // the same code works when testing against the source, I'm not sure why.
if (res && res.error) { if (res && res.error) {
@ -107,18 +109,17 @@ function catchError(p) {
throw new Error('Expected error to be instanceof Error.'); throw new Error('Expected error to be instanceof Error.');
} }
return error; return error;
} },
); );
} }
function promiseTo(fn) { function promiseTo(fn) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fn((error, result) => error ? reject(error) : resolve(result)); fn((error, result) => (error ? reject(error) : resolve(result)));
}); });
} }
describe('test harness', () => { describe('test harness', () => {
it('expects to catch errors', async () => { it('expects to catch errors', async () => {
let caught; let caught;
try { try {
@ -136,7 +137,9 @@ describe('test harness', () => {
} catch (error) { } catch (error) {
caught = error; caught = error;
} }
expect(caught && caught.message).to.equal('Expected error to be instanceof Error.'); expect(caught && caught.message).to.equal(
'Expected error to be instanceof Error.',
);
}); });
it('resolves callback promises', async () => { it('resolves callback promises', async () => {
@ -155,26 +158,29 @@ describe('test harness', () => {
} }
expect(caught).to.equal(rejectError); expect(caught).to.equal(rejectError);
}); });
}); });
const express = express4; const express = express4;
const version = 'modern'; const version = 'modern';
describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => { describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
describe('POST functionality', () => { describe('POST functionality', () => {
it('allows gzipped POST bodies', async () => { it('allows gzipped POST bodies', async () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress(() => ({ app.use(
schema: TestSchema '/graphql',
}))); graphqlExpress(() => ({
schema: TestSchema,
})),
);
const data = { query: '{ test(who: "World") }' }; const data = { query: '{ test(who: "World") }' };
const json = JSON.stringify(data); const json = JSON.stringify(data);
// TODO had to write "as any as Buffer" to make tsc accept it. Does it matter? // TODO had to write "as any as Buffer" to make tsc accept it. Does it matter?
const gzippedJson = await promiseTo(cb => zlib.gzip(json as any as Buffer, cb)); const gzippedJson = await promiseTo(cb =>
zlib.gzip((json as any) as Buffer, cb),
);
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
@ -185,8 +191,8 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
data: { data: {
test: 'Hello World' test: 'Hello World',
} },
}); });
}); });
@ -194,14 +200,19 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress(() => ({ app.use(
schema: TestSchema '/graphql',
}))); graphqlExpress(() => ({
schema: TestSchema,
})),
);
const data = { query: '{ test(who: "World") }' }; const data = { query: '{ test(who: "World") }' };
const json = JSON.stringify(data); const json = JSON.stringify(data);
// TODO had to write "as any as Buffer" to make tsc accept it. Does it matter? // TODO had to write "as any as Buffer" to make tsc accept it. Does it matter?
const deflatedJson = await promiseTo(cb => zlib.deflate(json as any as Buffer, cb)); const deflatedJson = await promiseTo(cb =>
zlib.deflate((json as any) as Buffer, cb),
);
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
@ -212,8 +223,8 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
data: { data: {
test: 'Hello World' test: 'Hello World',
} },
}); });
}); });
@ -227,16 +238,16 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
name: 'UploadedFile', name: 'UploadedFile',
fields: { fields: {
originalname: { type: GraphQLString }, originalname: { type: GraphQLString },
mimetype: { type: GraphQLString } mimetype: { type: GraphQLString },
} },
}); });
const TestMutationSchema = new GraphQLSchema({ const TestMutationSchema = new GraphQLSchema({
query: new GraphQLObjectType({ query: new GraphQLObjectType({
name: 'QueryRoot', name: 'QueryRoot',
fields: { fields: {
test: { type: GraphQLString } test: { type: GraphQLString },
} },
}), }),
mutation: new GraphQLObjectType({ mutation: new GraphQLObjectType({
name: 'MutationRoot', name: 'MutationRoot',
@ -248,10 +259,10 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
// file directly, but presumably you might return a Promise // file directly, but presumably you might return a Promise
// to go store the file somewhere first. // to go store the file somewhere first.
return rootValue.request.file; return rootValue.request.file;
} },
} },
} },
}) }),
}); });
const app = express(); const app = express();
@ -262,28 +273,34 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
// Providing the request as part of `rootValue` allows it to // Providing the request as part of `rootValue` allows it to
// be accessible from within Schema resolve functions. // be accessible from within Schema resolve functions.
app.use('/graphql', graphqlExpress(req => { app.use(
'/graphql',
graphqlExpress(req => {
return { return {
schema: TestMutationSchema, schema: TestMutationSchema,
rootValue: { request: req } rootValue: { request: req },
}; };
})); }),
);
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.field('query', `mutation TestMutation { .field(
'query',
`mutation TestMutation {
uploadFile { originalname, mimetype } uploadFile { originalname, mimetype }
}`) }`,
)
.attach('file', __filename); .attach('file', __filename);
return req.then((response) => { return req.then(response => {
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
data: { data: {
uploadFile: { uploadFile: {
originalname: 'apolloServerHttp.test.js', originalname: 'apolloServerHttp.test.js',
mimetype: 'application/javascript' mimetype: 'application/javascript',
} },
} },
}); });
}); });
}); });
@ -294,9 +311,12 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress({ app.use(
schema: TestSchema '/graphql',
})); graphqlExpress({
schema: TestSchema,
}),
);
const response = await request(app) const response = await request(app)
.post('/graphql') .post('/graphql')
@ -308,11 +328,13 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
data: null, data: null,
errors: [ { errors: [
{
message: 'Throws!', message: 'Throws!',
locations: [{ line: 1, column: 2 }], locations: [{ line: 1, column: 2 }],
path:["thrower"] path: ['thrower'],
} ] },
],
}); });
}); });
@ -320,9 +342,12 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress({ app.use(
schema: TestSchema '/graphql',
})); graphqlExpress({
schema: TestSchema,
}),
);
const response = await request(app) const response = await request(app)
.post('/graphql') .post('/graphql')
@ -332,29 +357,38 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
expect(response.status).to.equal(400); expect(response.status).to.equal(400);
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
errors: [ { errors: [
message: 'Cannot query field \"notExists\" on type \"QueryRoot\".', {
message: 'Cannot query field "notExists" on type "QueryRoot".',
locations: [{ line: 1, column: 2 }], locations: [{ line: 1, column: 2 }],
} ] },
],
}); });
}); });
it('handles type validation (GET)', async () => { it('handles type validation (GET)', async () => {
const app = express(); const app = express();
app.use('/graphql', require('connect-query')(), graphqlExpress({ app.use(
schema: TestSchema '/graphql',
})); require('connect-query')(),
graphqlExpress({
schema: TestSchema,
}),
);
const response = await request(app) const response = await request(app)
.get('/graphql').query({ query: '{notExists}' }); .get('/graphql')
.query({ query: '{notExists}' });
expect(response.status).to.equal(400); expect(response.status).to.equal(400);
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
errors: [ { errors: [
message: 'Cannot query field \"notExists\" on type \"QueryRoot\".', {
message: 'Cannot query field "notExists" on type "QueryRoot".',
locations: [{ line: 1, column: 2 }], locations: [{ line: 1, column: 2 }],
} ] },
],
}); });
}); });
@ -362,9 +396,12 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress({ app.use(
schema: TestSchema '/graphql',
})); graphqlExpress({
schema: TestSchema,
}),
);
const response = await request(app) const response = await request(app)
.post('/graphql') .post('/graphql')
@ -379,12 +416,15 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress({ app.use(
'/graphql',
graphqlExpress({
schema: TestSchema, schema: TestSchema,
formatError(error) { formatError(error) {
return { message: 'Custom error format: ' + error.message }; return { message: 'Custom error format: ' + error.message };
} },
})); }),
);
const response = await request(app) const response = await request(app)
.post('/graphql') .post('/graphql')
@ -395,9 +435,11 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
data: null, data: null,
errors: [ { errors: [
{
message: 'Custom error format: Throws!', message: 'Custom error format: Throws!',
} ] },
],
}); });
}); });
@ -405,16 +447,19 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress({ app.use(
'/graphql',
graphqlExpress({
schema: TestSchema, schema: TestSchema,
formatError(error) { formatError(error) {
return { return {
message: error.message, message: error.message,
locations: error.locations, locations: error.locations,
stack: 'Stack trace' stack: 'Stack trace',
}; };
} },
})); }),
);
const response = await request(app) const response = await request(app)
.post('/graphql') .post('/graphql')
@ -425,11 +470,13 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
data: null, data: null,
errors: [ { errors: [
{
message: 'Throws!', message: 'Throws!',
locations: [{ line: 1, column: 2 }], locations: [{ line: 1, column: 2 }],
stack: 'Stack trace', stack: 'Stack trace',
} ] },
],
}); });
}); });
@ -440,11 +487,14 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
app.use('/graphql', graphqlExpress({ schema: TestSchema })); app.use('/graphql', graphqlExpress({ schema: TestSchema }));
const response = await request(app) const response = await request(app)
.put('/graphql').query({ query: '{test}' }); .put('/graphql')
.query({ query: '{test}' });
expect(response.status).to.equal(405); expect(response.status).to.equal(405);
expect(response.headers.allow).to.equal('GET, POST'); expect(response.headers.allow).to.equal('GET, POST');
return expect(response.text).to.contain('Apollo Server supports only GET/POST requests.'); return expect(response.text).to.contain(
'Apollo Server supports only GET/POST requests.',
);
}); });
}); });
@ -452,11 +502,11 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const AlwaysInvalidRule = function(context) { const AlwaysInvalidRule = function(context) {
return { return {
enter() { enter() {
context.reportError(new GraphQLError( context.reportError(
'AlwaysInvalidRule was really invalid!' new GraphQLError('AlwaysInvalidRule was really invalid!'),
)); );
return BREAK; return BREAK;
} },
}; };
}; };
@ -464,26 +514,28 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
const app = express(); const app = express();
app.use('/graphql', bodyParser.json()); app.use('/graphql', bodyParser.json());
app.use('/graphql', graphqlExpress({ app.use(
'/graphql',
graphqlExpress({
schema: TestSchema, schema: TestSchema,
validationRules: [AlwaysInvalidRule], validationRules: [AlwaysInvalidRule],
})); }),
);
const response = await request(app) const response = await request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: '{thrower}', query: '{thrower}',
}) });
expect(response.status).to.equal(400); expect(response.status).to.equal(400);
expect(JSON.parse(response.text)).to.deep.equal({ expect(JSON.parse(response.text)).to.deep.equal({
errors: [ errors: [
{ {
message: 'AlwaysInvalidRule was really invalid!' message: 'AlwaysInvalidRule was really invalid!',
}, },
] ],
}); });
}); });
}); });
}); });

View file

@ -3,7 +3,10 @@ import * as bodyParser from 'body-parser';
import { graphqlConnect, graphiqlConnect } from './connectApollo'; import { graphqlConnect, graphiqlConnect } from './connectApollo';
import 'mocha'; import 'mocha';
import testSuite, { schema as Schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema as Schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
function createConnectApp(options: CreateAppOptions = {}) { function createConnectApp(options: CreateAppOptions = {}) {
const app = connect(); const app = connect();

View file

@ -1,7 +1,10 @@
import * as express from 'express'; import * as express from 'express';
import * as bodyParser from 'body-parser'; import * as bodyParser from 'body-parser';
import { graphqlExpress, graphiqlExpress } from './expressApollo'; import { graphqlExpress, graphiqlExpress } from './expressApollo';
import testSuite, { schema as Schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema as Schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
import { expect } from 'chai'; import { expect } from 'chai';
import { GraphQLOptions } from 'apollo-server-core'; import { GraphQLOptions } from 'apollo-server-core';
import 'mocha'; import 'mocha';
@ -23,12 +26,15 @@ function createApp(options: CreateAppOptions = {}) {
describe('expressApollo', () => { describe('expressApollo', () => {
it('throws error if called without schema', function() { it('throws error if called without schema', function() {
expect(() => graphqlExpress(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.'); expect(() => graphqlExpress(undefined as GraphQLOptions)).to.throw(
'Apollo Server requires options.',
);
}); });
it('throws an error if called with more than one argument', function() { it('throws an error if called with more than one argument', function() {
expect(() => (<any>graphqlExpress)({}, 'x')).to.throw( expect(() => (<any>graphqlExpress)({}, 'x')).to.throw(
'Apollo Server expects exactly one argument, got 2'); 'Apollo Server expects exactly one argument, got 2',
);
}); });
}); });

View file

@ -1,10 +1,16 @@
import * as express from 'express'; import * as express from 'express';
import * as url from 'url'; import * as url from 'url';
import { GraphQLOptions, HttpQueryError, runHttpQuery } from 'apollo-server-core'; import {
GraphQLOptions,
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
export interface ExpressGraphQLOptionsFunction { export interface ExpressGraphQLOptionsFunction {
(req?: express.Request, res?: express.Response): GraphQLOptions | Promise<GraphQLOptions>; (req?: express.Request, res?: express.Response):
| GraphQLOptions
| Promise<GraphQLOptions>;
} }
// Design principles: // Design principles:
@ -16,14 +22,18 @@ export interface ExpressHandler {
(req: express.Request, res: express.Response, next): void; (req: express.Request, res: express.Response, next): void;
} }
export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFunction): ExpressHandler { export function graphqlExpress(
options: GraphQLOptions | ExpressGraphQLOptionsFunction,
): ExpressHandler {
if (!options) { if (!options) {
throw new Error('Apollo Server requires options.'); throw new Error('Apollo Server requires options.');
} }
if (arguments.length > 1) { if (arguments.length > 1) {
// TODO: test this // TODO: test this
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`); throw new Error(
`Apollo Server expects exactly one argument, got ${arguments.length}`,
);
} }
return (req: express.Request, res: express.Response, next): void => { return (req: express.Request, res: express.Response, next): void => {
@ -31,18 +41,20 @@ export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFu
method: req.method, method: req.method,
options: options, options: options,
query: req.method === 'POST' ? req.body : req.query, query: req.method === 'POST' ? req.body : req.query,
}).then((gqlResponse) => { }).then(
gqlResponse => {
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Length', Buffer.byteLength(gqlResponse, 'utf8')); res.setHeader('Content-Length', Buffer.byteLength(gqlResponse, 'utf8'));
res.write(gqlResponse); res.write(gqlResponse);
res.end(); res.end();
}, (error: HttpQueryError) => { },
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) { if ('HttpQueryError' !== error.name) {
return next(error); return next(error);
} }
if (error.headers) { if (error.headers) {
Object.keys(error.headers).forEach((header) => { Object.keys(error.headers).forEach(header => {
res.setHeader(header, error.headers[header]); res.setHeader(header, error.headers[header]);
}); });
} }
@ -50,12 +62,15 @@ export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFu
res.statusCode = error.statusCode; res.statusCode = error.statusCode;
res.write(error.message); res.write(error.message);
res.end(); res.end();
}); },
);
}; };
} }
export interface ExpressGraphiQLOptionsFunction { export interface ExpressGraphiQLOptionsFunction {
(req?: express.Request): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>; (req?: express.Request):
| GraphiQL.GraphiQLData
| Promise<GraphiQL.GraphiQLData>;
} }
/* This middleware returns the html for the GraphiQL interactive query UI /* This middleware returns the html for the GraphiQL interactive query UI
@ -69,13 +84,18 @@ export interface ExpressGraphiQLOptionsFunction {
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI * - (optional) result: the result of the query to pre-fill in the GraphiQL UI
*/ */
export function graphiqlExpress(options: GraphiQL.GraphiQLData | ExpressGraphiQLOptionsFunction) { export function graphiqlExpress(
options: GraphiQL.GraphiQLData | ExpressGraphiQLOptionsFunction,
) {
return (req: express.Request, res: express.Response, next) => { return (req: express.Request, res: express.Response, next) => {
const query = req.url && url.parse(req.url, true).query; const query = req.url && url.parse(req.url, true).query;
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => { GraphiQL.resolveGraphiQLString(query, options, req).then(
graphiqlString => {
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
res.write(graphiqlString); res.write(graphiqlString);
res.end(); res.end();
}, error => next(error)); },
error => next(error),
);
}; };
} }

View file

@ -6,7 +6,4 @@ export {
graphiqlExpress, graphiqlExpress,
} from './expressApollo'; } from './expressApollo';
export { export { graphqlConnect, graphiqlConnect } from './connectApollo';
graphqlConnect,
graphiqlConnect,
} from './connectApollo';

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -63,5 +63,4 @@ Apollo Server is built with the following principles in mind:
* **Simplicity**: by keeping things simple, Apollo Server is easier to use, easier to contribute to, and more secure * **Simplicity**: by keeping things simple, Apollo Server is easier to use, easier to contribute to, and more secure
* **Performance**: Apollo Server is well-tested and production-ready - no modifications needed * **Performance**: Apollo Server is well-tested and production-ready - no modifications needed
Anyone is welcome to contribute to Apollo 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! Anyone is welcome to contribute to Apollo 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!

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-hapi" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-hapi"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Hapi", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Hapi",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -2,7 +2,10 @@ import * as hapi from 'hapi';
import { graphqlHapi, graphiqlHapi } from './hapiApollo'; import { graphqlHapi, graphiqlHapi } from './hapiApollo';
import 'mocha'; import 'mocha';
import testSuite, { schema as Schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema as Schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
async function createApp(options: CreateAppOptions) { async function createApp(options: CreateAppOptions) {
const server = new hapi.Server({ const server = new hapi.Server({
@ -22,7 +25,9 @@ async function createApp(options: CreateAppOptions) {
plugin: graphiqlHapi, plugin: graphiqlHapi,
options: { options: {
path: '/graphiql', path: '/graphiql',
graphiqlOptions: (options && options.graphiqlOptions) || { endpointURL: '/graphql' }, graphiqlOptions: (options && options.graphiqlOptions) || {
endpointURL: '/graphql',
},
}, },
}); });
@ -35,7 +40,7 @@ async function destroyApp(app) {
if (!app || !app.close) { if (!app || !app.close) {
return; return;
} }
await new Promise((resolve) => app.close(resolve)); await new Promise(resolve => app.close(resolve));
} }
describe('integration:Hapi', () => { describe('integration:Hapi', () => {

View file

@ -1,7 +1,11 @@
import * as Boom from 'boom'; import * as Boom from 'boom';
import { Server, Response, Request, ReplyNoContinue } from 'hapi'; import { Server, Response, Request, ReplyNoContinue } from 'hapi';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
import { GraphQLOptions, runHttpQuery, HttpQueryError } from 'apollo-server-core'; import {
GraphQLOptions,
runHttpQuery,
HttpQueryError,
} from 'apollo-server-core';
export interface IRegister { export interface IRegister {
(server: Server, options: any): void; (server: Server, options: any): void;
@ -61,7 +65,7 @@ const graphqlHapi: IPlugin = {
const err = new Boom(error.message, { statusCode: error.statusCode }); const err = new Boom(error.message, { statusCode: error.statusCode });
if (error.headers) { if (error.headers) {
Object.keys(error.headers).forEach((header) => { Object.keys(error.headers).forEach(header => {
err.output.headers[header] = error.headers[header]; err.output.headers[header] = error.headers[header];
}); });
} }
@ -96,7 +100,11 @@ const graphiqlHapi: IPlugin = {
path: options.path || '/graphiql', path: options.path || '/graphiql',
config: options.route || {}, config: options.route || {},
handler: async (request, h) => { handler: async (request, h) => {
const graphiqlString = await GraphiQL.resolveGraphiQLString(request.query, options.graphiqlOptions, request); const graphiqlString = await GraphiQL.resolveGraphiQLString(
request.query,
options.graphiqlOptions,
request,
);
const response = h.response(graphiqlString); const response = h.response(graphiqlString);
response.type('text/html'); response.type('text/html');

View file

@ -1,6 +1,9 @@
export { IRegister, export {
IRegister,
HapiOptionsFunction, HapiOptionsFunction,
HapiPluginOptions, HapiPluginOptions,
HapiGraphiQLOptionsFunction, HapiGraphiQLOptionsFunction,
HapiGraphiQLPluginOptions, HapiGraphiQLPluginOptions,
graphqlHapi, graphiqlHapi } from './hapiApollo'; graphqlHapi,
graphiqlHapi,
} from './hapiApollo';

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -10,7 +10,8 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-integration-testsuite" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-integration-testsuite"
}, },
"keywords": [], "keywords": [],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",

View file

@ -118,8 +118,12 @@ export const schema = new GraphQLSchema({
export interface CreateAppOptions { export interface CreateAppOptions {
excludeParser?: boolean; excludeParser?: boolean;
graphqlOptions?: GraphQLOptions | {(): GraphQLOptions | Promise<GraphQLOptions>}; graphqlOptions?:
graphiqlOptions?: GraphiQL.GraphiQLData | {(): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>}; | GraphQLOptions
| { (): GraphQLOptions | Promise<GraphQLOptions> };
graphiqlOptions?:
| GraphiQL.GraphiQLData
| { (): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData> };
} }
export interface CreateAppFunc { export interface CreateAppFunc {
@ -147,7 +151,9 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
describe('graphqlHTTP', () => { describe('graphqlHTTP', () => {
it('can be called with an options function', async () => { it('can be called with an options function', async () => {
app = await createApp({graphqlOptions: (): GraphQLOptions => ({schema})}); app = await createApp({
graphqlOptions: (): GraphQLOptions => ({ schema }),
});
const expected = { const expected = {
testString: 'it works', testString: 'it works',
}; };
@ -156,18 +162,20 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
.send({ .send({
query: 'query test{ testString }', query: 'query test{ testString }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
}); });
it('can be called with an options function that returns a promise', async () => { it('can be called with an options function that returns a promise', async () => {
app = await createApp({ graphqlOptions: () => { app = await createApp({
graphqlOptions: () => {
return new Promise(resolve => { return new Promise(resolve => {
resolve({ schema }); resolve({ schema });
}); });
}}); },
});
const expected = { const expected = {
testString: 'it works', testString: 'it works',
}; };
@ -176,23 +184,25 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
.send({ .send({
query: 'query test{ testString }', query: 'query test{ testString }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
}); });
it('throws an error if options promise is rejected', async () => { it('throws an error if options promise is rejected', async () => {
app = await createApp({ graphqlOptions: () => { app = await createApp({
return Promise.reject({}) as any as GraphQLOptions; graphqlOptions: () => {
}}); return (Promise.reject({}) as any) as GraphQLOptions;
},
});
const expected = 'Invalid options'; const expected = 'Invalid options';
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testString }', query: 'query test{ testString }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(500); expect(res.status).to.equal(500);
return expect(res.error.text).to.contain(expected); return expect(res.error.text).to.contain(expected);
}); });
@ -203,7 +213,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
const req = request(app) const req = request(app)
.head('/graphql') .head('/graphql')
.send(); .send();
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(405); expect(res.status).to.equal(405);
expect(res.headers['allow']).to.equal('GET, POST'); expect(res.headers['allow']).to.equal('GET, POST');
}); });
@ -214,7 +224,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send(); .send();
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(500); expect(res.status).to.equal(500);
return expect(res.error.text).to.contain('POST body missing.'); return expect(res.error.text).to.contain('POST body missing.');
}); });
@ -222,9 +232,8 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('throws an error if GET query is missing', async () => { it('throws an error if GET query is missing', async () => {
app = await createApp(); app = await createApp();
const req = request(app) const req = request(app).get(`/graphql`);
.get(`/graphql`); return req.then(res => {
return req.then((res) => {
expect(res.status).to.equal(400); expect(res.status).to.equal(400);
return expect(res.error.text).to.contain('GET query missing.'); return expect(res.error.text).to.contain('GET query missing.');
}); });
@ -239,8 +248,9 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'query test{ testString }', query: 'query test{ testString }',
}; };
const req = request(app) const req = request(app)
.get('/graphql').query(query); .get('/graphql')
return req.then((res) => { .query(query);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -255,8 +265,9 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: '{ testString }', query: '{ testString }',
}; };
const req = request(app) const req = request(app)
.get('/graphql').query(query); .get('/graphql')
return req.then((res) => { .query(query);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -268,11 +279,14 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'mutation test{ testMutation(echo: "ping") }', query: 'mutation test{ testMutation(echo: "ping") }',
}; };
const req = request(app) const req = request(app)
.get('/graphql').query(query); .get('/graphql')
return req.then((res) => { .query(query);
return req.then(res => {
expect(res.status).to.equal(405); expect(res.status).to.equal(405);
expect(res.headers['allow']).to.equal('POST'); expect(res.headers['allow']).to.equal('POST');
return expect(res.error.text).to.contain('GET supports only query operation'); return expect(res.error.text).to.contain(
'GET supports only query operation',
);
}); });
}); });
@ -290,11 +304,14 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
}`, }`,
}; };
const req = request(app) const req = request(app)
.get('/graphql').query(query); .get('/graphql')
return req.then((res) => { .query(query);
return req.then(res => {
expect(res.status).to.equal(405); expect(res.status).to.equal(405);
expect(res.headers['allow']).to.equal('POST'); expect(res.headers['allow']).to.equal('POST');
return expect(res.error.text).to.contain('GET supports only query operation'); return expect(res.error.text).to.contain(
'GET supports only query operation',
);
}); });
}); });
@ -308,8 +325,9 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
testArgument: 'hello world', testArgument: 'hello world',
}; };
const req = request(app) const req = request(app)
.get('/graphql').query(query); .get('/graphql')
return req.then((res) => { .query(query);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -325,7 +343,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
.send({ .send({
query: 'query test{ testString }', query: 'query test{ testString }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -342,7 +360,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'query test($echo: String){ testArgument(echo: $echo) }', query: 'query test($echo: String){ testArgument(echo: $echo) }',
variables: { echo: 'world' }, variables: { echo: 'world' },
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -359,7 +377,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'query test($echo: String!){ testArgument(echo: $echo) }', query: 'query test($echo: String!){ testArgument(echo: $echo) }',
variables: '{ "echo": "world" }', variables: '{ "echo": "world" }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -373,9 +391,11 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'query test($echo: String!){ testArgument(echo: $echo) }', query: 'query test($echo: String!){ testArgument(echo: $echo) }',
variables: '{ echo: "world" }', variables: '{ echo: "world" }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(400); expect(res.status).to.equal(400);
return expect(res.error.text).to.contain('Variables are invalid JSON.'); return expect(res.error.text).to.contain(
'Variables are invalid JSON.',
);
}); });
}); });
@ -393,7 +413,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
variables: { echo: 'world' }, variables: { echo: 'world' },
operationName: 'test2', operationName: 'test2',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -404,9 +424,11 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ query: introspectionQuery }); .send({ query: introspectionQuery });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data.__schema.types[0].fields[0].name).to.equal('testString'); return expect(
res.body.data.__schema.types[0].fields[0].name,
).to.equal('testString');
}); });
}); });
@ -426,7 +448,8 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
]; ];
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send([{ .send([
{
query: ` query: `
query test($echo: String){ testArgument(echo: $echo) } query test($echo: String){ testArgument(echo: $echo) }
query test2{ testString }`, query test2{ testString }`,
@ -438,8 +461,9 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query testX($echo: String){ testArgument(echo: $echo) }`, query testX($echo: String){ testArgument(echo: $echo) }`,
variables: { echo: 'yellow' }, variables: { echo: 'yellow' },
operationName: 'testX', operationName: 'testX',
}]); },
return req.then((res) => { ]);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected); return expect(res.body).to.deep.equal(expected);
}); });
@ -456,14 +480,16 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
]; ];
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send([{ .send([
{
query: ` query: `
query test($echo: String){ testArgument(echo: $echo) } query test($echo: String){ testArgument(echo: $echo) }
query test2{ testString }`, query test2{ testString }`,
variables: { echo: 'world' }, variables: { echo: 'world' },
operationName: 'test2', operationName: 'test2',
}]); },
return req.then((res) => { ]);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected); return expect(res.body).to.deep.equal(expected);
}); });
@ -481,22 +507,26 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
}); });
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send(Array(parallels).fill({ .send(
Array(parallels).fill({
query: `query test($delay: Int!) { testStringWithDelay(delay: $delay) }`, query: `query test($delay: Int!) { testStringWithDelay(delay: $delay) }`,
operationName: 'test', operationName: 'test',
variables: { delay: delayPerReq }, variables: { delay: delayPerReq },
})); }),
return req.then((res) => { );
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected); return expect(res.body).to.deep.equal(expected);
}); });
}); });
it('clones batch context', async () => { it('clones batch context', async () => {
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
context: { testField: 'expected' }, context: { testField: 'expected' },
}}); },
});
const expected = [ const expected = [
{ {
data: { data: {
@ -511,12 +541,15 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
]; ];
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send([{ .send([
{
query: 'query test{ testContext }', query: 'query test{ testContext }',
}, { },
{
query: 'query test{ testContext }', query: 'query test{ testContext }',
}]); },
return req.then((res) => { ]);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected); return expect(res.body).to.deep.equal(expected);
}); });
@ -524,13 +557,15 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('executes batch context if it is a function', async () => { it('executes batch context if it is a function', async () => {
let callCount = 0; let callCount = 0;
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
context: () => { context: () => {
callCount++; callCount++;
return ({testField: 'expected'}); return { testField: 'expected' };
}, },
}}); },
});
const expected = [ const expected = [
{ {
data: { data: {
@ -545,12 +580,15 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
]; ];
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send([{ .send([
{
query: 'query test{ testContext }', query: 'query test{ testContext }',
}, { },
{
query: 'query test{ testContext }', query: 'query test{ testContext }',
}]); },
return req.then((res) => { ]);
return req.then(res => {
expect(callCount).to.equal(2); expect(callCount).to.equal(2);
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected); return expect(res.body).to.deep.equal(expected);
@ -568,19 +606,22 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'mutation test($echo: String){ testMutation(echo: $echo) }', query: 'mutation test($echo: String){ testMutation(echo: $echo) }',
variables: { echo: 'world' }, variables: { echo: 'world' },
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
}); });
it('applies the formatResponse function', async () => { it('applies the formatResponse function', async () => {
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
formatResponse(response) { formatResponse(response) {
response['extensions'] = { it: 'works' }; return response; response['extensions'] = { it: 'works' };
return response;
}, },
}}); },
});
const expected = { it: 'works' }; const expected = { it: 'works' };
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
@ -588,7 +629,7 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: 'mutation test($echo: String){ testMutation(echo: $echo) }', query: 'mutation test($echo: String){ testMutation(echo: $echo) }',
variables: { echo: 'world' }, variables: { echo: 'world' },
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.extensions).to.deep.equal(expected); return expect(res.body.extensions).to.deep.equal(expected);
}); });
@ -596,16 +637,18 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('passes the context to the resolver', async () => { it('passes the context to the resolver', async () => {
const expected = 'context works'; const expected = 'context works';
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
context: { testField: expected }, context: { testField: expected },
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testContext }', query: 'query test{ testContext }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data.testContext).to.equal(expected); return expect(res.body.data.testContext).to.equal(expected);
}); });
@ -613,16 +656,18 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('passes the rootValue to the resolver', async () => { it('passes the rootValue to the resolver', async () => {
const expected = 'it passes rootValue'; const expected = 'it passes rootValue';
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
rootValue: expected, rootValue: expected,
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testRootValue }', query: 'query test{ testRootValue }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data.testRootValue).to.equal(expected); return expect(res.body.data.testRootValue).to.equal(expected);
}); });
@ -630,15 +675,17 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('returns errors', async () => { it('returns errors', async () => {
const expected = 'Secret error message'; const expected = 'Secret error message';
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testError }', query: 'query test{ testError }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.errors[0].message).to.equal(expected); return expect(res.body.errors[0].message).to.equal(expected);
}); });
@ -646,35 +693,41 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('applies formatError if provided', async () => { it('applies formatError if provided', async () => {
const expected = '--blank--'; const expected = '--blank--';
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
formatError: (err) => ({ message: expected }), formatError: err => ({ message: expected }),
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testError }', query: 'query test{ testError }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.errors[0].message).to.equal(expected); return expect(res.body.errors[0].message).to.equal(expected);
}); });
}); });
it('sends internal server error when formatError fails', async () => { it('sends internal server error when formatError fails', async () => {
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
formatError: (err) => { formatError: err => {
throw new Error('I should be caught'); throw new Error('I should be caught');
}, },
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testError }', query: 'query test{ testError }',
}); });
return req.then((res) => { return req.then(res => {
return expect(res.body.errors[0].message).to.equal('Internal server error'); return expect(res.body.errors[0].message).to.equal(
'Internal server error',
);
}); });
}); });
@ -683,16 +736,18 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
const stackTrace = []; const stackTrace = [];
const origError = console.error; const origError = console.error;
console.error = (...args) => stackTrace.push(args); console.error = (...args) => stackTrace.push(args);
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
debug: true, debug: true,
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testError }', query: 'query test{ testError }',
}); });
return req.then((res) => { return req.then(res => {
console.error = origError; console.error = origError;
return expect(stackTrace[0][0]).to.match(expected); return expect(stackTrace[0][0]).to.match(expected);
}); });
@ -701,16 +756,18 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('sends stack trace to error log if debug mode is set', async () => { it('sends stack trace to error log if debug mode is set', async () => {
const logStub = stub(console, 'error'); const logStub = stub(console, 'error');
const expected = /at resolveFieldValueOrError/; const expected = /at resolveFieldValueOrError/;
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
debug: true, debug: true,
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testError }', query: 'query test{ testError }',
}); });
return req.then((res) => { return req.then(res => {
logStub.restore(); logStub.restore();
expect(logStub.callCount).to.equal(1); expect(logStub.callCount).to.equal(1);
return expect(logStub.getCall(0).args[0]).to.match(expected); return expect(logStub.getCall(0).args[0]).to.match(expected);
@ -727,33 +784,37 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
}, },
}; };
}; };
app = await createApp({graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
validationRules: [alwaysInvalidRule], validationRules: [alwaysInvalidRule],
}}); },
});
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
query: 'query test{ testString }', query: 'query test{ testString }',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(400); expect(res.status).to.equal(400);
return expect(res.body.errors[0].message).to.equal(expected); return expect(res.body.errors[0].message).to.equal(expected);
}); });
}); });
}); });
describe('renderGraphiQL', () => { describe('renderGraphiQL', () => {
it('presents GraphiQL when accepting HTML', async () => { it('presents GraphiQL when accepting HTML', async () => {
app = await createApp({graphiqlOptions: { app = await createApp({
graphiqlOptions: {
endpointURL: '/graphql', endpointURL: '/graphql',
}}); },
});
const req = request(app) const req = request(app)
.get('/graphiql').query('query={test}') .get('/graphiql')
.query('query={test}')
.set('Accept', 'text/html'); .set('Accept', 'text/html');
return req.then((response) => { return req.then(response => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.type).to.equal('text/html'); expect(response.type).to.equal('text/html');
expect(response.text).to.include('{test}'); expect(response.text).to.include('{test}');
@ -763,58 +824,70 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
}); });
it('allows options to be a function', async () => { it('allows options to be a function', async () => {
app = await createApp({graphiqlOptions: () => ({ app = await createApp({
graphiqlOptions: () => ({
endpointURL: '/graphql', endpointURL: '/graphql',
})}); }),
});
const req = request(app) const req = request(app)
.get('/graphiql') .get('/graphiql')
.set('Accept', 'text/html'); .set('Accept', 'text/html');
return req.then((response) => { return req.then(response => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
}); });
}); });
it('handles options function errors', async () => { it('handles options function errors', async () => {
app = await createApp({graphiqlOptions: () => { app = await createApp({
graphiqlOptions: () => {
throw new Error('I should be caught'); throw new Error('I should be caught');
}}); },
});
const req = request(app) const req = request(app)
.get('/graphiql') .get('/graphiql')
.set('Accept', 'text/html'); .set('Accept', 'text/html');
return req.then((response) => { return req.then(response => {
expect(response.status).to.equal(500); expect(response.status).to.equal(500);
}); });
}); });
it('presents options variables', async () => { it('presents options variables', async () => {
app = await createApp({graphiqlOptions: { app = await createApp({
graphiqlOptions: {
endpointURL: '/graphql', endpointURL: '/graphql',
variables: { key: 'optionsValue' }, variables: { key: 'optionsValue' },
}}); },
});
const req = request(app) const req = request(app)
.get('/graphiql') .get('/graphiql')
.set('Accept', 'text/html'); .set('Accept', 'text/html');
return req.then((response) => { return req.then(response => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.text.replace(/\s/g, '')).to.include('variables:"{\\n\\"key\\":\\"optionsValue\\"\\n}"'); expect(response.text.replace(/\s/g, '')).to.include(
'variables:"{\\n\\"key\\":\\"optionsValue\\"\\n}"',
);
}); });
}); });
it('presents query variables over options variables', async () => { it('presents query variables over options variables', async () => {
app = await createApp({graphiqlOptions: { app = await createApp({
graphiqlOptions: {
endpointURL: '/graphql', endpointURL: '/graphql',
variables: { key: 'optionsValue' }, variables: { key: 'optionsValue' },
}}); },
});
const req = request(app) const req = request(app)
.get('/graphiql?variables={"key":"queryValue"}') .get('/graphiql?variables={"key":"queryValue"}')
.set('Accept', 'text/html'); .set('Accept', 'text/html');
return req.then((response) => { return req.then(response => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
expect(response.text.replace(/\s/g, '')).to.include('variables:"{\\n\\"key\\":\\"queryValue\\"\\n}"'); expect(response.text.replace(/\s/g, '')).to.include(
'variables:"{\\n\\"key\\":\\"queryValue\\"\\n}"',
);
}); });
}); });
}); });
@ -823,20 +896,22 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('works with formatParams', async () => { it('works with formatParams', async () => {
const store = new OperationStore(schema); const store = new OperationStore(schema);
store.put('query testquery{ testString }'); store.put('query testquery{ testString }');
app = await createApp({ graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
formatParams(params) { formatParams(params) {
params['query'] = store.get(params.operationName); params['query'] = store.get(params.operationName);
return params; return params;
}, },
}}); },
});
const expected = { testString: 'it works' }; const expected = { testString: 'it works' };
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send({ .send({
operationName: 'testquery', operationName: 'testquery',
}); });
return req.then((res) => { return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
@ -845,7 +920,8 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
it('can reject non-whitelisted queries', async () => { it('can reject non-whitelisted queries', async () => {
const store = new OperationStore(schema); const store = new OperationStore(schema);
store.put('query testquery{ testString }'); store.put('query testquery{ testString }');
app = await createApp({ graphqlOptions: { app = await createApp({
graphqlOptions: {
schema, schema,
formatParams(params) { formatParams(params) {
if (params.query) { if (params.query) {
@ -854,25 +930,34 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
params['query'] = store.get(params.operationName); params['query'] = store.get(params.operationName);
return params; return params;
}, },
}}); },
const expected = [{ });
const expected = [
{
data: { data: {
testString: 'it works', testString: 'it works',
}, },
}, { },
errors: [{ {
errors: [
{
message: 'Must not provide query, only operationName', message: 'Must not provide query, only operationName',
}], },
}]; ],
},
];
const req = request(app) const req = request(app)
.post('/graphql') .post('/graphql')
.send([{ .send([
{
operationName: 'testquery', operationName: 'testquery',
}, { },
{
query: '{ testString }', query: '{ testString }',
}]); },
return req.then((res) => { ]);
return req.then(res => {
expect(res.status).to.equal(200); expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected); return expect(res.body).to.deep.equal(expected);
}); });
@ -887,8 +972,9 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
query: '{ testString }', query: '{ testString }',
}; };
const req = request(app) const req = request(app)
.get('/bogus-route').query(query); .get('/bogus-route')
return req.then((res) => { .query(query);
return req.then(res => {
expect(res.status).to.equal(404); expect(res.status).to.equal(404);
}); });
}); });

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -42,21 +42,28 @@ You can also use `apollo-server-koa` for hosting the [GraphiQL](https://github.c
import { graphiqlKoa } from 'apollo-server-koa'; import { graphiqlKoa } from 'apollo-server-koa';
// Setup the /graphiql route to show the GraphiQL UI // Setup the /graphiql route to show the GraphiQL UI
router.get('/graphiql', graphiqlKoa({ router.get(
endpointURL: '/graphql' // a POST endpoint that GraphiQL will make the actual requests to '/graphiql',
})); graphiqlKoa({
endpointURL: '/graphql', // a POST endpoint that GraphiQL will make the actual requests to
}),
);
``` ```
In case your GraphQL endpoint is protected via authentication, or if you need to pass other custom headers in the request that GraphiQL makes, you can use the [`passHeader`](https://github.com/apollographql/apollo-server/blob/v1.0.2/packages/apollo-server-module-graphiql/src/renderGraphiQL.ts#L17) option  a string that will be added to the request header object. In case your GraphQL endpoint is protected via authentication, or if you need to pass other custom headers in the request that GraphiQL makes, you can use the [`passHeader`](https://github.com/apollographql/apollo-server/blob/v1.0.2/packages/apollo-server-module-graphiql/src/renderGraphiQL.ts#L17) option  a string that will be added to the request header object.
For example: For example:
```js ```js
import { graphiqlKoa } from 'apollo-server-koa'; import { graphiqlKoa } from 'apollo-server-koa';
router.get('/graphiql', graphiqlKoa({ router.get(
'/graphiql',
graphiqlKoa({
endpointURL: '/graphql', endpointURL: '/graphql',
passHeader: `'Authorization': 'Bearer lorem ipsum'` passHeader: `'Authorization': 'Bearer lorem ipsum'`,
})); }),
);
``` ```
## Principles ## Principles
@ -67,5 +74,4 @@ Apollo Server is built with the following principles in mind:
* **Simplicity**: by keeping things simple, Apollo Server is easier to use, easier to contribute to, and more secure * **Simplicity**: by keeping things simple, Apollo Server is easier to use, easier to contribute to, and more secure
* **Performance**: Apollo Server is well-tested and production-ready - no modifications needed * **Performance**: Apollo Server is well-tested and production-ready - no modifications needed
Anyone is welcome to contribute to Apollo 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! Anyone is welcome to contribute to Apollo 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!

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-koa" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-koa"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Koa", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Koa",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -6,7 +6,10 @@ import { GraphQLOptions } from 'apollo-server-core';
import { expect } from 'chai'; import { expect } from 'chai';
import * as http from 'http'; import * as http from 'http';
import testSuite, { schema as Schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema as Schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
function createApp(options: CreateAppOptions = {}) { function createApp(options: CreateAppOptions = {}) {
const app = new koa(); const app = new koa();
@ -33,12 +36,15 @@ function destroyApp(app) {
describe('koaApollo', () => { describe('koaApollo', () => {
it('throws error if called without schema', function() { it('throws error if called without schema', function() {
expect(() => graphqlKoa(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.'); expect(() => graphqlKoa(undefined as GraphQLOptions)).to.throw(
'Apollo Server requires options.',
);
}); });
it('throws an error if called with more than one argument', function() { it('throws an error if called with more than one argument', function() {
expect(() => (<any>graphqlKoa)({}, 'x')).to.throw( expect(() => (<any>graphqlKoa)({}, 'x')).to.throw(
'Apollo Server expects exactly one argument, got 2'); 'Apollo Server expects exactly one argument, got 2',
);
}); });
}); });

View file

@ -1,5 +1,9 @@
import * as koa from 'koa'; import * as koa from 'koa';
import { GraphQLOptions, HttpQueryError, runHttpQuery } from 'apollo-server-core'; import {
GraphQLOptions,
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
export interface KoaGraphQLOptionsFunction { export interface KoaGraphQLOptionsFunction {
@ -10,37 +14,45 @@ export interface KoaHandler {
(req: any, next): void; (req: any, next): void;
} }
export function graphqlKoa(options: GraphQLOptions | KoaGraphQLOptionsFunction): KoaHandler { export function graphqlKoa(
options: GraphQLOptions | KoaGraphQLOptionsFunction,
): KoaHandler {
if (!options) { if (!options) {
throw new Error('Apollo Server requires options.'); throw new Error('Apollo Server requires options.');
} }
if (arguments.length > 1) { if (arguments.length > 1) {
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`); throw new Error(
`Apollo Server expects exactly one argument, got ${arguments.length}`,
);
} }
return (ctx: koa.Context): Promise<void> => { return (ctx: koa.Context): Promise<void> => {
return runHttpQuery([ctx], { return runHttpQuery([ctx], {
method: ctx.request.method, method: ctx.request.method,
options: options, options: options,
query: ctx.request.method === 'POST' ? ctx.request.body : ctx.request.query, query:
}).then((gqlResponse) => { ctx.request.method === 'POST' ? ctx.request.body : ctx.request.query,
}).then(
gqlResponse => {
ctx.set('Content-Type', 'application/json'); ctx.set('Content-Type', 'application/json');
ctx.body = gqlResponse; ctx.body = gqlResponse;
}, (error: HttpQueryError) => { },
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) { if ('HttpQueryError' !== error.name) {
throw error; throw error;
} }
if (error.headers) { if (error.headers) {
Object.keys(error.headers).forEach((header) => { Object.keys(error.headers).forEach(header => {
ctx.set(header, error.headers[header]); ctx.set(header, error.headers[header]);
}); });
} }
ctx.status = error.statusCode; ctx.status = error.statusCode;
ctx.body = error.message; ctx.body = error.message;
}); },
);
}; };
} }
@ -48,15 +60,20 @@ export interface KoaGraphiQLOptionsFunction {
(ctx: koa.Context): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>; (ctx: koa.Context): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
} }
export function graphiqlKoa(options: GraphiQL.GraphiQLData | KoaGraphiQLOptionsFunction) { export function graphiqlKoa(
options: GraphiQL.GraphiQLData | KoaGraphiQLOptionsFunction,
) {
return (ctx: koa.Context) => { return (ctx: koa.Context) => {
const query = ctx.request.query; const query = ctx.request.query;
return GraphiQL.resolveGraphiQLString(query, options, ctx).then(graphiqlString => { return GraphiQL.resolveGraphiQLString(query, options, ctx).then(
graphiqlString => {
ctx.set('Content-Type', 'text/html'); ctx.set('Content-Type', 'text/html');
ctx.body = graphiqlString; ctx.body = graphiqlString;
}, error => { },
error => {
ctx.status = 500; ctx.status = 500;
ctx.body = error.message; ctx.body = error.message;
}); },
);
}; };
} }

View file

@ -5,8 +5,5 @@
"outDir": "./dist", "outDir": "./dist",
"types": [] "types": []
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -24,7 +24,7 @@ var server = require('apollo-server-lambda'),
exports.graphqlHandler = server.graphqlLambda({ schema: myGraphQLSchema }); exports.graphqlHandler = server.graphqlLambda({ schema: myGraphQLSchema });
exports.graphiqlHandler = server.graphiqlLambda({ exports.graphiqlHandler = server.graphiqlLambda({
endpointURL: '/Prod/graphql' endpointURL: '/Prod/graphql',
}); });
``` ```
@ -118,8 +118,8 @@ exports.graphqlHandler = server.graphqlLambda((event, context) => {
headers, headers,
functionName, functionName,
event, event,
context context,
} },
}; };
}); });
``` ```

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-lambda" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-lambda"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Lambda", "Javascript"],
"GraphQL",
"Apollo",
"Server",
"Lambda",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -1,16 +1,15 @@
import { graphqlLambda, graphiqlLambda } from './lambdaApollo'; import { graphqlLambda, graphiqlLambda } from './lambdaApollo';
import testSuite, { schema as Schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema as Schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
import { expect } from 'chai'; import { expect } from 'chai';
import { GraphQLOptions } from 'apollo-server-core'; import { GraphQLOptions } from 'apollo-server-core';
import 'mocha'; import 'mocha';
import * as url from 'url'; import * as url from 'url';
function createLambda(options: CreateAppOptions = {}) { function createLambda(options: CreateAppOptions = {}) {
let route, let route, handler, callback, event, context;
handler,
callback,
event,
context;
options.graphqlOptions = options.graphqlOptions || { schema: Schema }; options.graphqlOptions = options.graphqlOptions || { schema: Schema };
if (options.graphiqlOptions) { if (options.graphiqlOptions) {
@ -59,12 +58,15 @@ function createLambda(options: CreateAppOptions = {}) {
describe('lambdaApollo', () => { describe('lambdaApollo', () => {
it('throws error if called without schema', function() { it('throws error if called without schema', function() {
expect(() => graphqlLambda(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.'); expect(() => graphqlLambda(undefined as GraphQLOptions)).to.throw(
'Apollo Server requires options.',
);
}); });
it('throws an error if called with more than one argument', function() { it('throws an error if called with more than one argument', function() {
expect(() => (<any>graphqlLambda)({}, {})).to.throw( expect(() => (<any>graphqlLambda)({}, {})).to.throw(
'Apollo Server expects exactly one argument, got 2'); 'Apollo Server expects exactly one argument, got 2',
);
}); });
}); });

View file

@ -3,7 +3,9 @@ import { GraphQLOptions, runHttpQuery } from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
export interface LambdaGraphQLOptionsFunction { export interface LambdaGraphQLOptionsFunction {
(event: any, context: lambda.Context): GraphQLOptions | Promise<GraphQLOptions>; (event: any, context: lambda.Context):
| GraphQLOptions
| Promise<GraphQLOptions>;
} }
// Design principles: // Design principles:
@ -19,17 +21,26 @@ export interface IHeaders {
[header: string]: string | number; [header: string]: string | number;
} }
export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFunction ): LambdaHandler { export function graphqlLambda(
options: GraphQLOptions | LambdaGraphQLOptionsFunction,
): LambdaHandler {
if (!options) { if (!options) {
throw new Error('Apollo Server requires options.'); throw new Error('Apollo Server requires options.');
} }
if (arguments.length > 1) { if (arguments.length > 1) {
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`); throw new Error(
`Apollo Server expects exactly one argument, got ${arguments.length}`,
);
} }
return async (event, lambdaContext: lambda.Context, callback: lambda.Callback) => { return async (
let query = (event.httpMethod === 'POST') ? event.body : event.queryStringParameters, event,
lambdaContext: lambda.Context,
callback: lambda.Callback,
) => {
let query =
event.httpMethod === 'POST' ? event.body : event.queryStringParameters,
statusCode: number = null, statusCode: number = null,
gqlResponse = null, gqlResponse = null,
headers: { [headerName: string]: string } = {}; headers: { [headerName: string]: string } = {};
@ -55,20 +66,19 @@ export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFun
statusCode = error.statusCode; statusCode = error.statusCode;
gqlResponse = error.message; gqlResponse = error.message;
} finally { } finally {
callback( callback(null, {
null, statusCode: statusCode,
{ headers: headers,
'statusCode': statusCode, body: gqlResponse,
'headers': headers, });
'body': gqlResponse,
},
);
} }
}; };
} }
export interface LambdaGraphiQLOptionsFunction { export interface LambdaGraphiQLOptionsFunction {
(event: any, context: lambda.Context): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>; (event: any, context: lambda.Context):
| GraphiQL.GraphiQLData
| Promise<GraphiQL.GraphiQLData>;
} }
/* This Lambda Function Handler returns the html for the GraphiQL interactive query UI /* This Lambda Function Handler returns the html for the GraphiQL interactive query UI
@ -82,25 +92,27 @@ export interface LambdaGraphiQLOptionsFunction {
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI * - (optional) result: the result of the query to pre-fill in the GraphiQL UI
*/ */
export function graphiqlLambda(options: GraphiQL.GraphiQLData | LambdaGraphiQLOptionsFunction) { export function graphiqlLambda(
options: GraphiQL.GraphiQLData | LambdaGraphiQLOptionsFunction,
) {
return (event, lambdaContext: lambda.Context, callback: lambda.Callback) => { return (event, lambdaContext: lambda.Context, callback: lambda.Callback) => {
const query = event.queryStringParameters; const query = event.queryStringParameters;
GraphiQL.resolveGraphiQLString(query, options, event, lambdaContext).then(graphiqlString => { GraphiQL.resolveGraphiQLString(query, options, event, lambdaContext).then(
callback( graphiqlString => {
null, callback(null, {
{ statusCode: 200,
'statusCode': 200, headers: {
'headers': {
'Content-Type': 'text/html', 'Content-Type': 'text/html',
}, },
'body': graphiqlString, body: graphiqlString,
});
}, },
); error => {
}, error => {
callback(null, { callback(null, {
statusCode: 500, statusCode: 500,
body: error.message, body: error.message,
}); });
}); },
);
}; };
} }

View file

@ -3,15 +3,8 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"],
"node_modules/@types" "types": ["@types/node"]
],
"types": [
"@types/node"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -27,8 +27,8 @@ const server = micro(
get('/graphql', graphqlHandler), get('/graphql', graphqlHandler),
post('/graphql', graphqlHandler), post('/graphql', graphqlHandler),
get('/graphiql', graphiqlHandler), get('/graphiql', graphiqlHandler),
(req, res) => send(res, 404, 'not found') (req, res) => send(res, 404, 'not found'),
) ),
); );
server.listen(3000); server.listen(3000);

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-micro" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-micro"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Micro", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Micro",
"Server",
"Javascript"
],
"author": "Nick Nance <nance.nick@gmail.email>", "author": "Nick Nance <nance.nick@gmail.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -2,13 +2,26 @@ import { microGraphql, microGraphiql } from './microApollo';
import 'mocha'; import 'mocha';
import micro, { send } from 'micro'; import micro, { send } from 'micro';
import { router, get, post, put, patch, del, head, options as opts } from 'microrouter'; import {
import testSuite, { schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; router,
get,
post,
put,
patch,
del,
head,
options as opts,
} from 'microrouter';
import testSuite, {
schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
function createApp(options: CreateAppOptions) { function createApp(options: CreateAppOptions) {
const graphqlOptions = (options && options.graphqlOptions) || { schema }; const graphqlOptions = (options && options.graphqlOptions) || { schema };
const graphiqlOptions = (options && options.graphiqlOptions) || { endpointURL: '/graphql' }; const graphiqlOptions = (options && options.graphiqlOptions) || {
endpointURL: '/graphql',
};
const graphqlHandler = microGraphql(graphqlOptions); const graphqlHandler = microGraphql(graphqlOptions);
const graphiqlHandler = microGraphiql(graphiqlOptions); const graphiqlHandler = microGraphiql(graphiqlOptions);

View file

@ -1,4 +1,8 @@
import { GraphQLOptions, HttpQueryError, runHttpQuery } from 'apollo-server-core'; import {
GraphQLOptions,
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
import { createError, json, RequestHandler } from 'micro'; import { createError, json, RequestHandler } from 'micro';
import * as url from 'url'; import * as url from 'url';
@ -8,13 +12,17 @@ export interface MicroGraphQLOptionsFunction {
(req?: IncomingMessage): GraphQLOptions | Promise<GraphQLOptions>; (req?: IncomingMessage): GraphQLOptions | Promise<GraphQLOptions>;
} }
export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFunction): RequestHandler { export function microGraphql(
options: GraphQLOptions | MicroGraphQLOptionsFunction,
): RequestHandler {
if (!options) { if (!options) {
throw new Error('Apollo Server requires options.'); throw new Error('Apollo Server requires options.');
} }
if (arguments.length > 1) { if (arguments.length > 1) {
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`); throw new Error(
`Apollo Server expects exactly one argument, got ${arguments.length}`,
);
} }
return async function(req: IncomingMessage, res: ServerResponse) { return async function(req: IncomingMessage, res: ServerResponse) {
@ -41,7 +49,7 @@ export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFuncti
} catch (error) { } catch (error) {
if ('HttpQueryError' === error.name) { if ('HttpQueryError' === error.name) {
if (error.headers) { if (error.headers) {
Object.keys(error.headers).forEach((header) => { Object.keys(error.headers).forEach(header => {
res.setHeader(header, error.headers[header]); res.setHeader(header, error.headers[header]);
}); });
} }
@ -57,20 +65,27 @@ export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFuncti
} }
export interface MicroGraphiQLOptionsFunction { export interface MicroGraphiQLOptionsFunction {
(req?: IncomingMessage): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>; (req?: IncomingMessage):
| GraphiQL.GraphiQLData
| Promise<GraphiQL.GraphiQLData>;
} }
export function microGraphiql(options: GraphiQL.GraphiQLData | MicroGraphiQLOptionsFunction): RequestHandler { export function microGraphiql(
options: GraphiQL.GraphiQLData | MicroGraphiQLOptionsFunction,
): RequestHandler {
return (req: IncomingMessage, res: ServerResponse) => { return (req: IncomingMessage, res: ServerResponse) => {
const query = req.url && url.parse(req.url, true).query || {}; const query = (req.url && url.parse(req.url, true).query) || {};
return GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => { return GraphiQL.resolveGraphiQLString(query, options, req).then(
graphiqlString => {
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
res.write(graphiqlString); res.write(graphiqlString);
res.end(); res.end();
}, error => { },
error => {
res.statusCode = 500; res.statusCode = 500;
res.write(error.message); res.write(error.message);
res.end(); res.end();
}); },
);
}; };
} }

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,14 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-module-graphiql" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-module-graphiql"
}, },
"keywords": [ "keywords": ["GraphQL", "GraphiQL", "Apollo", "Javascript"],
"GraphQL",
"GraphiQL",
"Apollo",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -21,15 +21,15 @@
*/ */
export type GraphiQLData = { export type GraphiQLData = {
endpointURL: string, endpointURL: string;
subscriptionsEndpoint?: string, subscriptionsEndpoint?: string;
query?: string, query?: string;
variables?: Object, variables?: Object;
operationName?: string, operationName?: string;
result?: Object, result?: Object;
passHeader?: string, passHeader?: string;
editorTheme?: string, editorTheme?: string;
websocketConnectionParams?: Object, websocketConnectionParams?: Object;
}; };
// Current latest version of GraphiQL. // Current latest version of GraphiQL.
@ -44,15 +44,18 @@ function safeSerialize(data) {
export function renderGraphiQL(data: GraphiQLData): string { export function renderGraphiQL(data: GraphiQLData): string {
const endpointURL = data.endpointURL; const endpointURL = data.endpointURL;
const endpointWs = endpointURL.startsWith('ws://') || endpointURL.startsWith('wss://'); const endpointWs =
endpointURL.startsWith('ws://') || endpointURL.startsWith('wss://');
const subscriptionsEndpoint = data.subscriptionsEndpoint; const subscriptionsEndpoint = data.subscriptionsEndpoint;
const usingHttp = !endpointWs; const usingHttp = !endpointWs;
const usingWs = endpointWs || !!subscriptionsEndpoint; const usingWs = endpointWs || !!subscriptionsEndpoint;
const endpointURLWs = usingWs && (endpointWs ? endpointURL : subscriptionsEndpoint); const endpointURLWs =
usingWs && (endpointWs ? endpointURL : subscriptionsEndpoint);
const queryString = data.query; const queryString = data.query;
const variablesString = const variablesString = data.variables
data.variables ? JSON.stringify(data.variables, null, 2) : null; ? JSON.stringify(data.variables, null, 2)
: null;
const resultString = null; const resultString = null;
const operationName = data.operationName; const operationName = data.operationName;
const passHeader = data.passHeader ? data.passHeader : ''; const passHeader = data.passHeader ? data.passHeader : '';
@ -80,18 +83,26 @@ export function renderGraphiQL(data: GraphiQLData): string {
<script src="//unpkg.com/react@15.6.1/dist/react.min.js"></script> <script src="//unpkg.com/react@15.6.1/dist/react.min.js"></script>
<script src="//unpkg.com/react-dom@15.6.1/dist/react-dom.min.js"></script> <script src="//unpkg.com/react-dom@15.6.1/dist/react-dom.min.js"></script>
<script src="//unpkg.com/graphiql@${GRAPHIQL_VERSION}/graphiql.min.js"></script> <script src="//unpkg.com/graphiql@${GRAPHIQL_VERSION}/graphiql.min.js"></script>
${usingEditorTheme ? ${
`<link href="//cdn.jsdelivr.net/npm/codemirror@5/theme/${editorTheme}.min.css" rel="stylesheet" />` usingEditorTheme
: ''} ? `<link href="//cdn.jsdelivr.net/npm/codemirror@5/theme/${editorTheme}.min.css" rel="stylesheet" />`
${usingHttp ? : ''
`<script src="//cdn.jsdelivr.net/fetch/2.0.1/fetch.min.js"></script>` }
: ''} ${
${usingWs ? usingHttp
`<script src="//unpkg.com/subscriptions-transport-ws@${SUBSCRIPTIONS_TRANSPORT_VERSION}/browser/client.js"></script>` ? `<script src="//cdn.jsdelivr.net/fetch/2.0.1/fetch.min.js"></script>`
: ''} : ''
${usingWs && usingHttp ? }
'<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>' ${
: ''} usingWs
? `<script src="//unpkg.com/subscriptions-transport-ws@${SUBSCRIPTIONS_TRANSPORT_VERSION}/browser/client.js"></script>`
: ''
}
${
usingWs && usingHttp
? '<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>'
: ''
}
</head> </head>
<body> <body>
@ -125,16 +136,26 @@ export function renderGraphiQL(data: GraphiQLData): string {
} }
} }
${usingWs ? ` ${
usingWs
? `
var subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient('${endpointURLWs}', { var subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient('${endpointURLWs}', {
reconnect: true${websocketConnectionParams ? `, reconnect: true${
connectionParams: ${JSON.stringify(websocketConnectionParams)}` : '' } websocketConnectionParams
? `,
connectionParams: ${JSON.stringify(websocketConnectionParams)}`
: ''
}
}); });
var graphQLWSFetcher = subscriptionsClient.request.bind(subscriptionsClient); var graphQLWSFetcher = subscriptionsClient.request.bind(subscriptionsClient);
` : ''} `
: ''
}
${usingHttp ? ` ${
usingHttp
? `
// We don't use safe-serialize for location, because it's not client input. // We don't use safe-serialize for location, because it's not client input.
var fetchURL = locationQuery(otherParams, '${endpointURL}'); var fetchURL = locationQuery(otherParams, '${endpointURL}');
@ -159,14 +180,20 @@ export function renderGraphiQL(data: GraphiQLData): string {
} }
}); });
} }
` : ''} `
: ''
}
${usingWs && usingHttp ? ` ${
usingWs && usingHttp
? `
var fetcher = var fetcher =
window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLHttpFetcher); window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLHttpFetcher);
` : ` `
: `
var fetcher = ${usingWs ? 'graphQLWSFetcher' : 'graphQLHttpFetcher'}; var fetcher = ${usingWs ? 'graphQLWSFetcher' : 'graphQLHttpFetcher'};
`} `
}
// When the query and variables string is edited, update the URL bar so // When the query and variables string is edited, update the URL bar so
// that it can be easily shared. // that it can be easily shared.

View file

@ -1,16 +1,19 @@
import { GraphiQLData, renderGraphiQL } from './renderGraphiQL'; import { GraphiQLData, renderGraphiQL } from './renderGraphiQL';
export type GraphiQLParams = { export type GraphiQLParams = {
query?: string, query?: string;
variables?: string, variables?: string;
operationName?: string, operationName?: string;
}; };
function isOptionsFunction(arg: GraphiQLData | Function): arg is Function { function isOptionsFunction(arg: GraphiQLData | Function): arg is Function {
return typeof arg === 'function'; return typeof arg === 'function';
} }
async function resolveGraphiQLOptions(options: GraphiQLData | Function, ...args): Promise<GraphiQLData> { async function resolveGraphiQLOptions(
options: GraphiQLData | Function,
...args
): Promise<GraphiQLData> {
if (isOptionsFunction(options)) { if (isOptionsFunction(options)) {
try { try {
return await options(...args); return await options(...args);
@ -31,12 +34,16 @@ function createGraphiQLParams(query: any): GraphiQLParams {
}; };
} }
function createGraphiQLData(params: GraphiQLParams, options: GraphiQLData): GraphiQLData { function createGraphiQLData(
params: GraphiQLParams,
options: GraphiQLData,
): GraphiQLData {
return { return {
endpointURL: options.endpointURL, endpointURL: options.endpointURL,
subscriptionsEndpoint: options.subscriptionsEndpoint, subscriptionsEndpoint: options.subscriptionsEndpoint,
query: params.query || options.query, query: params.query || options.query,
variables: params.variables && JSON.parse(params.variables) || options.variables, variables:
(params.variables && JSON.parse(params.variables)) || options.variables,
operationName: params.operationName || options.operationName, operationName: params.operationName || options.operationName,
passHeader: options.passHeader, passHeader: options.passHeader,
editorTheme: options.editorTheme, editorTheme: options.editorTheme,
@ -44,7 +51,11 @@ function createGraphiQLData(params: GraphiQLParams, options: GraphiQLData): Grap
}; };
} }
export async function resolveGraphiQLString(query: any = {}, options: GraphiQLData | Function, ...args): Promise<string> { export async function resolveGraphiQLString(
query: any = {},
options: GraphiQLData | Function,
...args
): Promise<string> {
const graphiqlParams = createGraphiQLParams(query); const graphiqlParams = createGraphiQLParams(query);
const graphiqlOptions = await resolveGraphiQLOptions(options, ...args); const graphiqlOptions = await resolveGraphiQLOptions(options, ...args);
const graphiqlData = createGraphiQLData(graphiqlParams, graphiqlOptions); const graphiqlData = createGraphiQLData(graphiqlParams, graphiqlOptions);

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,14 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-module-operation-store" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-module-operation-store"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Operation Store", "Javascript"],
"GraphQL",
"Apollo",
"Operation Store",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -1,8 +1,6 @@
import 'mocha'; import 'mocha';
import { import { expect } from 'chai';
expect,
} from 'chai';
import { import {
GraphQLSchema, GraphQLSchema,
@ -105,7 +103,9 @@ describe('operationStore', () => {
`; `;
const store = new OperationStore(schema); const store = new OperationStore(schema);
return expect(() => store.put(query)).to.throw(/OperationDefinitionNode must contain only one definition/); return expect(() => store.put(query)).to.throw(
/OperationDefinitionNode must contain only one definition/,
);
}); });
it('throws an error if there is no operationDefinition found', () => { it('throws an error if there is no operationDefinition found', () => {

View file

@ -17,7 +17,9 @@ export class OperationStore {
} }
public put(operation: string | DocumentNode): void { public put(operation: string | DocumentNode): void {
function isOperationDefinition(definition): definition is OperationDefinitionNode { function isOperationDefinition(
definition,
): definition is OperationDefinitionNode {
return definition.kind === Kind.OPERATION_DEFINITION; return definition.kind === Kind.OPERATION_DEFINITION;
} }
@ -25,19 +27,27 @@ export class OperationStore {
return typeof definition === 'string'; return typeof definition === 'string';
} }
const ast = isString(operation) ? parse(operation as string) : operation as DocumentNode; const ast = isString(operation)
? parse(operation as string)
: (operation as DocumentNode);
const definitions = ast.definitions.filter(isOperationDefinition) as OperationDefinitionNode[]; const definitions = ast.definitions.filter(
isOperationDefinition,
) as OperationDefinitionNode[];
if (definitions.length === 0) { if (definitions.length === 0) {
throw new Error('OperationDefinitionNode must contain at least one definition'); throw new Error(
'OperationDefinitionNode must contain at least one definition',
);
} }
if (definitions.length > 1) { if (definitions.length > 1) {
throw new Error('OperationDefinitionNode must contain only one definition'); throw new Error(
'OperationDefinitionNode must contain only one definition',
);
} }
const validationErrors = validate(this.schema, ast); const validationErrors = validate(this.schema, ast);
if (validationErrors.length > 0) { if (validationErrors.length > 0) {
const messages = validationErrors.map((e) => e.message); const messages = validationErrors.map(e => e.message);
const err = new Error(`Validation Errors:\n${messages.join('\n')}`); const err = new Error(`Validation Errors:\n${messages.join('\n')}`);
err['originalErrors'] = validationErrors; err['originalErrors'] = validationErrors;
throw err; throw err;

View file

@ -3,12 +3,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"typeRoots": [ "typeRoots": ["node_modules/@types"]
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -20,7 +20,7 @@ import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify';
const PORT = 3000; const PORT = 3000;
const server = restify.createServer({ const server = restify.createServer({
title: 'Apollo Server' title: 'Apollo Server',
}); });
const graphQLOptions = { schema: myGraphQLSchema }; const graphQLOptions = { schema: myGraphQLSchema };

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-restify" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-restify"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Restify", "Javascript"],
"GraphQL",
"Apollo",
"Server",
"Restify",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -1,7 +1,10 @@
import 'mocha'; import 'mocha';
import * as restify from 'restify'; import * as restify from 'restify';
import { graphiqlRestify, graphqlRestify } from './restifyApollo'; import { graphiqlRestify, graphqlRestify } from './restifyApollo';
import testSuite, { schema, CreateAppOptions } from 'apollo-server-integration-testsuite'; import testSuite, {
schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
import { expect } from 'chai'; import { expect } from 'chai';
import { GraphQLOptions } from 'apollo-server-core'; import { GraphQLOptions } from 'apollo-server-core';
@ -28,12 +31,15 @@ function createApp(options: CreateAppOptions = {}) {
describe('graphqlRestify', () => { describe('graphqlRestify', () => {
it('throws error if called without schema', () => { it('throws error if called without schema', () => {
expect(() => graphqlRestify(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.'); expect(() => graphqlRestify(undefined as GraphQLOptions)).to.throw(
'Apollo Server requires options.',
);
}); });
it('throws an error if called with more than one argument', () => { it('throws an error if called with more than one argument', () => {
expect(() => (<any>graphqlRestify)({}, 'x')).to.throw( expect(() => (<any>graphqlRestify)({}, 'x')).to.throw(
'Apollo Server expects exactly one argument, got 2'); 'Apollo Server expects exactly one argument, got 2',
);
}); });
it('generates a function if the options are ok', () => { it('generates a function if the options are ok', () => {

View file

@ -1,10 +1,16 @@
import * as restify from 'restify'; import * as restify from 'restify';
import * as url from 'url'; import * as url from 'url';
import { GraphQLOptions, HttpQueryError, runHttpQuery } from 'apollo-server-core'; import {
GraphQLOptions,
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-module-graphiql'; import * as GraphiQL from 'apollo-server-module-graphiql';
export interface RestifyGraphQLOptionsFunction { export interface RestifyGraphQLOptionsFunction {
(req?: restify.Request, res?: restify.Response): GraphQLOptions | Promise<GraphQLOptions>; (req?: restify.Request, res?: restify.Response):
| GraphQLOptions
| Promise<GraphQLOptions>;
} }
// Design principles: // Design principles:
@ -16,32 +22,42 @@ export interface RestifyHandler {
(req: restify.Request, res: restify.Response, next: restify.Next): void; (req: restify.Request, res: restify.Response, next: restify.Next): void;
} }
export function graphqlRestify(options: GraphQLOptions | RestifyGraphQLOptionsFunction): RestifyHandler { export function graphqlRestify(
options: GraphQLOptions | RestifyGraphQLOptionsFunction,
): RestifyHandler {
if (!options) { if (!options) {
throw new Error('Apollo Server requires options.'); throw new Error('Apollo Server requires options.');
} }
if (arguments.length > 1) { if (arguments.length > 1) {
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`); throw new Error(
`Apollo Server expects exactly one argument, got ${arguments.length}`,
);
} }
return (req: restify.Request, res: restify.Response, next: restify.Next): void => { return (
req: restify.Request,
res: restify.Response,
next: restify.Next,
): void => {
runHttpQuery([req, res], { runHttpQuery([req, res], {
method: req.method, method: req.method,
options: options, options: options,
query: req.method === 'POST' ? req.body : req.query, query: req.method === 'POST' ? req.body : req.query,
}).then((gqlResponse) => { }).then(
gqlResponse => {
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
res.write(gqlResponse); res.write(gqlResponse);
res.end(); res.end();
next(); next();
}, (error: HttpQueryError) => { },
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) { if ('HttpQueryError' !== error.name) {
throw error; throw error;
} }
if (error.headers) { if (error.headers) {
Object.keys(error.headers).forEach((header) => { Object.keys(error.headers).forEach(header => {
res.setHeader(header, error.headers[header]); res.setHeader(header, error.headers[header]);
}); });
} }
@ -50,12 +66,15 @@ export function graphqlRestify(options: GraphQLOptions | RestifyGraphQLOptionsFu
res.write(error.message); res.write(error.message);
res.end(); res.end();
next(false); next(false);
}); },
);
}; };
} }
export interface RestifyGraphiQLOptionsFunction { export interface RestifyGraphiQLOptionsFunction {
(req?: restify.Request): GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>; (req?: restify.Request):
| GraphiQL.GraphiQLData
| Promise<GraphiQL.GraphiQLData>;
} }
/* This middleware returns the html for the GraphiQL interactive query UI /* This middleware returns the html for the GraphiQL interactive query UI
@ -69,19 +88,24 @@ export interface RestifyGraphiQLOptionsFunction {
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI * - (optional) result: the result of the query to pre-fill in the GraphiQL UI
*/ */
export function graphiqlRestify(options: GraphiQL.GraphiQLData | RestifyGraphiQLOptionsFunction) { export function graphiqlRestify(
options: GraphiQL.GraphiQLData | RestifyGraphiQLOptionsFunction,
) {
return (req: restify.Request, res: restify.Response, next: restify.Next) => { return (req: restify.Request, res: restify.Response, next: restify.Next) => {
const query = req.url && url.parse(req.url, true).query || {}; const query = (req.url && url.parse(req.url, true).query) || {};
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => { GraphiQL.resolveGraphiQLString(query, options, req).then(
graphiqlString => {
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
res.write(graphiqlString); res.write(graphiqlString);
res.end(); res.end();
next(); next();
}, error => { },
error => {
res.statusCode = 500; res.statusCode = 500;
res.write(error.message); res.write(error.message);
res.end(); res.end();
next(false); next(false);
}); },
);
}; };
} }

View file

@ -5,8 +5,5 @@
"outDir": "./dist", "outDir": "./dist",
"types": [] "types": []
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,14 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-core" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-core"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -1,7 +1,8 @@
{ {
"name": "graphql-server-express", "name": "graphql-server-express",
"version": "1.3.1", "version": "1.3.1",
"description": "Production-ready Node.js GraphQL server for Express and Connect", "description":
"Production-ready Node.js GraphQL server for Express and Connect",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"compile": "tsc", "compile": "tsc",
@ -9,7 +10,8 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-express" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-express"
}, },
"keywords": [ "keywords": [
"GraphQL", "GraphQL",

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-hapi" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-hapi"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Hapi", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Hapi",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-koa" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-koa"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Koa", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Koa",
"Server",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-lambda" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-lambda"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Lambda", "Javascript"],
"GraphQL",
"Apollo",
"Server",
"Lambda",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-micro" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-micro"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Micro", "Server", "Javascript"],
"GraphQL",
"Apollo",
"Micro",
"Server",
"Javascript"
],
"author": "Nick Nance <nance.nick@gmail.email>", "author": "Nick Nance <nance.nick@gmail.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,14 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-module-graphiql" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-module-graphiql"
}, },
"keywords": [ "keywords": ["GraphQL", "GraphiQL", "Apollo", "Javascript"],
"GraphQL",
"GraphiQL",
"Apollo",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,14 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-module-operation-store" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-module-operation-store"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Operation Store", "Javascript"],
"GraphQL",
"Apollo",
"Operation Store",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -9,15 +9,10 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-restify" "url":
"https://github.com/apollographql/apollo-server/tree/master/packages/graphql-server-restify"
}, },
"keywords": [ "keywords": ["GraphQL", "Apollo", "Server", "Restify", "Javascript"],
"GraphQL",
"Apollo",
"Server",
"Restify",
"Javascript"
],
"author": "Jonas Helfer <jonas@helfer.email>", "author": "Jonas Helfer <jonas@helfer.email>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {

View file

@ -4,8 +4,5 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"exclude": [ "exclude": ["node_modules", "dist"]
"node_modules",
"dist"
]
} }

View file

@ -1,17 +1,21 @@
const NODE_VERSION = process.version.split('.'); const NODE_VERSION = process.version.split('.');
const NODE_MAJOR_VERSION = parseInt(NODE_VERSION[0].replace(/^v/, '')); const NODE_MAJOR_VERSION = parseInt(NODE_VERSION[0].replace(/^v/, ''));
const NODE_MAJOR_REVISION = parseInt(NODE_VERSION[1]) const NODE_MAJOR_REVISION = parseInt(NODE_VERSION[1]);
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
require('../packages/apollo-server-core/dist/runQuery.test.js'); require('../packages/apollo-server-core/dist/runQuery.test.js');
require('../packages/apollo-server-module-operation-store/dist/operationStore.test'); require('../packages/apollo-server-module-operation-store/dist/operationStore.test');
(NODE_MAJOR_VERSION >= 7) && require('../packages/apollo-server-adonis/dist/adonisApollo.test'); NODE_MAJOR_VERSION >= 7 &&
require('../packages/apollo-server-adonis/dist/adonisApollo.test');
require('../packages/apollo-server-express/dist/expressApollo.test'); require('../packages/apollo-server-express/dist/expressApollo.test');
require('../packages/apollo-server-express/dist/connectApollo.test'); require('../packages/apollo-server-express/dist/connectApollo.test');
(NODE_MAJOR_VERSION >= 9 || (NODE_MAJOR_VERSION >= 8 && NODE_MAJOR_REVISION >= 9)) (NODE_MAJOR_VERSION >= 9 ||
&& require('../packages/apollo-server-hapi/dist/hapiApollo.test'); // Hapi 17 is 8.9+ (NODE_MAJOR_VERSION >= 8 && NODE_MAJOR_REVISION >= 9)) &&
(NODE_MAJOR_VERSION >= 6) && require('../packages/apollo-server-micro/dist/microApollo.test'); require('../packages/apollo-server-hapi/dist/hapiApollo.test'); // Hapi 17 is 8.9+
(NODE_MAJOR_VERSION >= 7) && require('../packages/apollo-server-koa/dist/koaApollo.test'); NODE_MAJOR_VERSION >= 6 &&
require('../packages/apollo-server-micro/dist/microApollo.test');
NODE_MAJOR_VERSION >= 7 &&
require('../packages/apollo-server-koa/dist/koaApollo.test');
require('../packages/apollo-server-lambda/dist/lambdaApollo.test'); require('../packages/apollo-server-lambda/dist/lambdaApollo.test');
require('../packages/apollo-server-azure-functions/dist/azureFunctionsApollo.test'); require('../packages/apollo-server-azure-functions/dist/azureFunctionsApollo.test');
require('../packages/apollo-server-express/dist/apolloServerHttp.test'); require('../packages/apollo-server-express/dist/apolloServerHttp.test');

View file

@ -10,8 +10,6 @@
"pretty": true, "pretty": true,
"removeComments": true, "removeComments": true,
"lib": ["es6", "esnext.asynciterable"], "lib": ["es6", "esnext.asynciterable"],
"types": [ "types": ["@types/node"]
"@types/node"
]
} }
} }

View file

@ -1,133 +0,0 @@
{
"rules": {
"align": [
false,
"parameters",
"arguments",
"statements"
],
"ban": false,
"class-name": true,
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"interface-name": false,
"jsdoc-format": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": true,
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": false,
"no-console": [
true,
"log",
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-null-keyword": false,
"no-require-imports": false,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-var-keyword": true,
"no-var-requires": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"radix": true,
"semicolon": [
true,
"always"
],
"switch-default": true,
"trailing-comma": [
true,
{
"multiline": "always",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef": [
false,
"call-signature",
"parameter",
"arrow-parameter",
"property-declaration",
"variable-declaration",
"member-variable-declaration"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "space",
"index-signature": "space",
"parameter": "space",
"property-declaration": "space",
"variable-declaration": "space"
}
],
"variable-name": [
true,
"check-format",
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}