mirror of
https://github.com/vale981/apollo-server
synced 2025-03-04 09:11:40 -05:00
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:
parent
e837e92fb2
commit
df51fd90da
100 changed files with 2606 additions and 2399 deletions
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -7,7 +7,7 @@
|
|||
|
||||
TODO:
|
||||
|
||||
- [ ] Update CHANGELOG.md with your change (include reference to issue & this PR)
|
||||
- [ ] Make sure all of the significant new logic is covered by tests
|
||||
- [ ] Rebase your changes on master so that they can be merged easily
|
||||
- [ ] Make sure all tests and linter rules pass
|
||||
* [ ] Update CHANGELOG.md with your change (include reference to issue & this PR)
|
||||
* [ ] Make sure all of the significant new logic is covered by tests
|
||||
* [ ] Rebase your changes on master so that they can be merged easily
|
||||
* [ ] Make sure all tests and linter rules pass
|
||||
|
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true
|
||||
}
|
71
CHANGELOG.md
71
CHANGELOG.md
|
@ -1,24 +1,29 @@
|
|||
# Changelog
|
||||
|
||||
### vNEXT
|
||||
|
||||
* Fix apollo-server-core runQuery breaks async_hooks tracking [PR #733](https://github.com/apollographql/apollo-server/pull/733)
|
||||
|
||||
### v1.3.0
|
||||
|
||||
* Added support for the vhost option for Hapi [PR #611](https://github.com/apollographql/apollo-server/pull/611)
|
||||
* Include readme for npm packages
|
||||
* 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)
|
||||
|
||||
### 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)
|
||||
* 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)
|
||||
|
||||
### 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)
|
||||
|
||||
### v1.1.1
|
||||
|
||||
* Added support for Azure Functions [#503](https://github.com/apollographql/apollo-server/pull/503)
|
||||
|
||||
### v1.1.0
|
||||
|
@ -36,62 +41,79 @@
|
|||
because it's a breaking change that shouldn't have been a patch update.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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)
|
||||
|
||||
### 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)
|
||||
* Updated & fixed typescript typings ([@helfer](https://github.com/helfer)) in [#440](https://github.com/apollographql/graphql-server/pull/440)
|
||||
|
||||
### v0.9.0
|
||||
|
||||
* Allow GraphiQLOptions to be a function ([@NeoPhi](https://github.com/NeoPhi)) on [#426](https://github.com/apollographql/graphql-server/pull/426)
|
||||
|
||||
### v0.8.5
|
||||
|
||||
* Fix: graphql-server-micro now properly returns response promises [#401](https://github.com/apollographql/graphql-server/pull/401)
|
||||
|
||||
### v0.8.4
|
||||
|
||||
### v0.8.3
|
||||
|
||||
### 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)
|
||||
|
||||
### 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)
|
||||
|
||||
### v0.8.0
|
||||
|
||||
* 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)
|
||||
* 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
|
||||
|
||||
* Fix include passHeader field that was accidentally removed
|
||||
|
||||
### v0.7.1
|
||||
|
||||
* Fix graphiql fetcher to use endpointURL parameter instead of hardcoded URI.[#365](https://github.com/apollographql/graphql-server/issues/356)
|
||||
|
||||
### v0.7.0
|
||||
|
||||
* 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)
|
||||
* 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)
|
||||
|
||||
### v0.6.0
|
||||
|
||||
* 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)
|
||||
* **Restify integration** ([@joelgriffith](https://github.com/joelgriffith)) on [#189](https://github.com/apollostack/graphql-server/pull/189)
|
||||
* run batched requests in parallel ([@DxCx](https://github.com/DxCx)) on [#273](https://github.com/apollostack/graphql-server/pull/273)
|
||||
* Fix GraphiQL options variables. Issue #193. ([@alanchristensen](https://github.com/alanchristensen)) on
|
||||
[PR #255](https://github.com/apollostack/apollo-server/pull/255)
|
||||
[PR #255](https://github.com/apollostack/apollo-server/pull/255)
|
||||
* 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
|
||||
|
||||
* add support for HTTP GET Method ([@DxCx](https://github.com/DxCx)) on [#180](https://github.com/apollostack/graphql-server/pull/180)
|
||||
|
||||
### v0.5.0
|
||||
|
||||
* Switch graphql typings for typescript to @types/graphql [#260](https://github.com/apollostack/graphql-server/pull/260)
|
||||
|
||||
### 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)
|
||||
* Upgrade to GraphQL-js 0.8.1.
|
||||
|
||||
|
@ -99,12 +121,12 @@
|
|||
|
||||
* **Restructure Apollo Server into 6 new packages, and rename to GraphQL Server** ([@DxCx](https://github.com/DxCx)) and ([@stubailo](https://github.com/stubailo)) in [#183](https://github.com/apollostack/graphql-server/pull/183) and [#164](https://github.com/apollostack/graphql-server/pull/183).
|
||||
* There are now 6 packages that make up the GraphQL server family:
|
||||
* `graphql-server-core`
|
||||
* `graphql-module-graphiql`
|
||||
* `graphql-module-operation-store`
|
||||
* `graphql-server-express`
|
||||
* `graphql-server-hapi`
|
||||
* `graphql-server-koa`
|
||||
* `graphql-server-core`
|
||||
* `graphql-module-graphiql`
|
||||
* `graphql-module-operation-store`
|
||||
* `graphql-server-express`
|
||||
* `graphql-server-hapi`
|
||||
* `graphql-server-koa`
|
||||
* Exports have been renamed. Everything that used to export `apollo*` now exports `graphql*`, for example `apolloExpress` has become `graphqlExpress`.
|
||||
* The repository is now managed using [Lerna](https://github.com/lerna/lerna).
|
||||
|
||||
|
@ -118,40 +140,47 @@
|
|||
* Clone context object for each query in a batch.
|
||||
|
||||
### 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)
|
||||
|
||||
### 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))
|
||||
|
||||
### v0.3.0
|
||||
|
||||
* 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)
|
||||
* Fixed query batching with Hapi integration. Issue #123 ([@nnance](https://github.com/nnance)) in
|
||||
[PR #127](https://github.com/apollostack/apollo-server/pull/127)
|
||||
* Add support for route options in Hapi integration. Issue #97. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #127](https://github.com/apollostack/apollo-server/pull/127)
|
||||
* Camelcase Hapi. Issue #129. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #132](https://github.com/apollostack/apollo-server/pull/132)
|
||||
[PR #127](https://github.com/apollostack/apollo-server/pull/127)
|
||||
* Fixed query batching with Hapi integration. Issue #123 ([@nnance](https://github.com/nnance)) in
|
||||
[PR #127](https://github.com/apollostack/apollo-server/pull/127)
|
||||
* Add support for route options in Hapi integration. Issue #97. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #127](https://github.com/apollostack/apollo-server/pull/127)
|
||||
* Camelcase Hapi. Issue #129. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #132](https://github.com/apollostack/apollo-server/pull/132)
|
||||
* Fix error handling when parsing variables parameter. Issue #130. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #131](https://github.com/apollostack/apollo-server/pull/131)
|
||||
[PR #131](https://github.com/apollostack/apollo-server/pull/131)
|
||||
* Improve logging function. Issue #79. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #136](https://github.com/apollostack/apollo-server/pull/136)
|
||||
[PR #136](https://github.com/apollostack/apollo-server/pull/136)
|
||||
* Output stack trace for errors in debug mode. Issue #111. ([@nnance](https://github.com/nnance)) in
|
||||
[PR #137](https://github.com/apollostack/apollo-server/pull/137)
|
||||
[PR #137](https://github.com/apollostack/apollo-server/pull/137)
|
||||
* 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
|
||||
|
||||
* 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))
|
||||
* Expose ApolloOptions as part of the public API.
|
||||
|
||||
### v0.2.5
|
||||
|
||||
* Made promise compatible with fibers ([@benjamn](https://github.com/benjamn) in [#92](https://github.com/apollostack/apollo-server/pull/92))
|
||||
|
||||
### 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))
|
||||
|
||||
### v0.2.1
|
||||
|
||||
* 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 Koa integration ([@HriBB](https://github.com/HriBB) in [#59](https://github.com/apollostack/apollo-server/pull/59))
|
||||
|
@ -166,10 +195,10 @@
|
|||
* Added `formatRequest` and `formatResponse` functions to apollo options.
|
||||
* Removed support for shorthand schema definitions, connectors and mocks (use `graphql-tools` instead)
|
||||
|
||||
|
||||
### v0.1.5
|
||||
|
||||
* 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)
|
||||
|
||||
### v.0.1.4
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ For a small bug fix change (less than 20 lines of code changed), feel free to op
|
|||
|
||||
### Suggesting features
|
||||
|
||||
Most of the features in Apollo came from suggestions by you, the community! We welcome any ideas about how to make Apollo better for your use case. Unless there is overwhelming demand for a feature, it might not get implemented immediately, but please include as much information as possible that will help people have a discussion about your proposal:
|
||||
Most of the features in Apollo came from suggestions by you, the community! We welcome any ideas about how to make Apollo better for your use case. Unless there is overwhelming demand for a feature, it might not get implemented immediately, but please include as much information as possible that will help people have a discussion about your proposal:
|
||||
|
||||
1. **Use case:** What are you trying to accomplish, in specific terms? Often, there might already be a good way to do what you need and a new feature is unnecessary, but it’s hard to know without information about the specific use case.
|
||||
2. **Could this be a plugin?** In many cases, a feature might be too niche to be included in the core of a library, and is better implemented as a companion package. If there isn’t a way to extend the library to do what you want, could we add additional plugin APIs? It’s important to make the case for why a feature should be part of the core functionality of the library.
|
||||
|
@ -57,8 +57,8 @@ Once there is a consensus on the need for a new feature, proceed as listed below
|
|||
|
||||
This includes:
|
||||
|
||||
- Big bug fixes
|
||||
- New features
|
||||
* Big bug fixes
|
||||
* New features
|
||||
|
||||
For significant changes to a repository, it’s 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, it’s good to reduce the amount of possible wasted effort by agreeing on an implementation design/plan first.
|
||||
|
||||
|
@ -85,5 +85,4 @@ It’s 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.
|
||||
|
||||
|
||||
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.
|
||||
|
|
16
DESIGN.md
16
DESIGN.md
|
@ -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.
|
||||
|
||||
|
||||
### Core
|
||||
|
||||
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:
|
||||
|
||||
- Express
|
||||
- Hapi
|
||||
- Connect
|
||||
- Koa
|
||||
- Restify
|
||||
- ...
|
||||
* Express
|
||||
* Hapi
|
||||
* Connect
|
||||
* Koa
|
||||
* Restify
|
||||
* ...
|
||||
|
||||
Framework integrations take care of parsing requests, submitting them to GraphQL Server’s 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 Server’s 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.
|
||||
|
||||
### Modules
|
||||
|
||||
Things that are not part of runQuery’s 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.
|
||||
|
|
82
README.md
82
README.md
|
@ -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!
|
||||
|
||||
where `<variant>` is one of the following:
|
||||
- `express`
|
||||
- `koa`
|
||||
- `hapi`
|
||||
- `restify`
|
||||
- `lambda`
|
||||
- `micro`
|
||||
- `azure-functions`
|
||||
- `adonis`
|
||||
|
||||
* `express`
|
||||
* `koa`
|
||||
* `hapi`
|
||||
* `restify`
|
||||
* `lambda`
|
||||
* `micro`
|
||||
* `azure-functions`
|
||||
* `adonis`
|
||||
|
||||
### Express
|
||||
|
||||
|
@ -60,6 +61,7 @@ app.listen(PORT);
|
|||
```
|
||||
|
||||
### Connect
|
||||
|
||||
```js
|
||||
import connect from 'connect';
|
||||
import bodyParser from 'body-parser';
|
||||
|
@ -82,7 +84,7 @@ http.createServer(app).listen(PORT);
|
|||
|
||||
### Hapi
|
||||
|
||||
Now with the Hapi plugins `graphqlHapi` and `graphiqlHapi` you can pass a route object that includes options to be applied to the route. The example below enables CORS on the `/graphql` route.
|
||||
Now with the Hapi plugins `graphqlHapi` and `graphiqlHapi` you can pass a route object that includes options to be applied to the route. The example below enables CORS on the `/graphql` route.
|
||||
|
||||
The code below requires Hapi 17 or higher.
|
||||
|
||||
|
@ -94,37 +96,38 @@ const HOST = 'localhost';
|
|||
const PORT = 3000;
|
||||
|
||||
async function StartServer() {
|
||||
const server = new Hapi.server({
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
});
|
||||
const server = new Hapi.server({
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
});
|
||||
|
||||
await server.register({
|
||||
plugin: graphqlHapi,
|
||||
options: {
|
||||
path: '/graphql',
|
||||
graphqlOptions: {
|
||||
schema: myGraphQLSchema,
|
||||
},
|
||||
route: {
|
||||
cors: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
await server.register({
|
||||
plugin: graphqlHapi,
|
||||
options: {
|
||||
path: '/graphql',
|
||||
graphqlOptions: {
|
||||
schema: myGraphQLSchema,
|
||||
},
|
||||
route: {
|
||||
cors: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await server.start();
|
||||
} catch (err) {
|
||||
console.log(`Error while starting server: ${err.message}`);
|
||||
}
|
||||
try {
|
||||
await server.start();
|
||||
} catch (err) {
|
||||
console.log(`Error while starting server: ${err.message}`);
|
||||
}
|
||||
|
||||
console.log(`Server running at: ${server.info.uri}`);
|
||||
console.log(`Server running at: ${server.info.uri}`);
|
||||
}
|
||||
|
||||
StartServer();
|
||||
```
|
||||
|
||||
### Koa
|
||||
|
||||
```js
|
||||
import koa from 'koa'; // koa@2
|
||||
import koaRouter from 'koa-router'; // koa-router@next
|
||||
|
@ -147,6 +150,7 @@ app.listen(PORT);
|
|||
```
|
||||
|
||||
### Restify
|
||||
|
||||
```js
|
||||
import restify from 'restify';
|
||||
import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify';
|
||||
|
@ -154,7 +158,7 @@ import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify';
|
|||
const PORT = 3000;
|
||||
|
||||
const server = restify.createServer({
|
||||
title: 'Apollo Server'
|
||||
title: 'Apollo Server',
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
```js
|
||||
var server = require("apollo-server-lambda");
|
||||
var server = require('apollo-server-lambda');
|
||||
|
||||
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
|
||||
|
||||
```js
|
||||
const server = require("apollo-server-micro");
|
||||
const server = require('apollo-server-micro');
|
||||
|
||||
module.exports = server.microGraphql({ schema: myGraphQLSchema });
|
||||
```
|
||||
|
@ -225,11 +229,11 @@ The `formatParams` function can be used in combination with the `OperationStore`
|
|||
const store = new OperationStore(Schema);
|
||||
store.put('query testquery{ testString }');
|
||||
graphqlOptions = {
|
||||
schema: Schema,
|
||||
formatParams(params) {
|
||||
params['query'] = store.get(params.operationName);
|
||||
return params;
|
||||
},
|
||||
schema: Schema,
|
||||
formatParams(params) {
|
||||
params['query'] = store.get(params.operationName);
|
||||
return params;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -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!
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
|
||||
## Proposed designs
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
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' }`.
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
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 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.
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"scripts": {
|
||||
"start": "hexo serve",
|
||||
"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/"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ const { makeExecutableSchema } = require('graphql-tools');
|
|||
const books = [
|
||||
{
|
||||
title: "Harry Potter and the Sorcerer's stone",
|
||||
author: 'J.K. Rowling'
|
||||
author: 'J.K. Rowling',
|
||||
},
|
||||
{
|
||||
title: 'Jurassic Park',
|
||||
author: 'Michael Crichton'
|
||||
}
|
||||
author: 'Michael Crichton',
|
||||
},
|
||||
];
|
||||
|
||||
// The GraphQL schema in string form
|
||||
|
@ -37,13 +37,13 @@ const typeDefs = `
|
|||
|
||||
// The resolvers
|
||||
const resolvers = {
|
||||
Query: { books: () => books }
|
||||
Query: { books: () => books },
|
||||
};
|
||||
|
||||
// Put together a schema
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs,
|
||||
resolvers
|
||||
resolvers,
|
||||
});
|
||||
|
||||
// Initialize the app
|
||||
|
|
|
@ -35,8 +35,8 @@ import { graphiqlExpress } from 'apollo-server-express';
|
|||
app.use(
|
||||
'/graphiql',
|
||||
graphiqlExpress({
|
||||
endpointURL: '/graphql'
|
||||
})
|
||||
endpointURL: '/graphql',
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
|
@ -50,8 +50,8 @@ import { graphiqlConnect } from 'apollo-server-express';
|
|||
app.use(
|
||||
'/graphiql',
|
||||
graphiqlConnect({
|
||||
endpointURL: '/graphql'
|
||||
})
|
||||
endpointURL: '/graphql',
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
|
@ -67,9 +67,9 @@ server.register({
|
|||
options: {
|
||||
path: '/graphiql',
|
||||
graphiqlOptions: {
|
||||
endpointURL: '/graphql'
|
||||
}
|
||||
}
|
||||
endpointURL: '/graphql',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
- Attach a GraphQL schema to your HTTP server to serve requests
|
||||
- Attach GraphQL and GraphiQL via separate middlewares, on different routes
|
||||
- Accept queries via GET or POST
|
||||
- Support HTTP query batching
|
||||
- Support Apollo Tracing to get performance information about your server
|
||||
- Support Apollo Cache Control to inform caching gateways such as Apollo Engine
|
||||
* Attach a GraphQL schema to your HTTP server to serve requests
|
||||
* Attach GraphQL and GraphiQL via separate middlewares, on different routes
|
||||
* Accept queries via GET or POST
|
||||
* Support HTTP query batching
|
||||
* Support Apollo Tracing to get performance information about your server
|
||||
* Support Apollo Cache Control to inform caching gateways such as Apollo Engine
|
||||
|
||||
<h2 id="principles">Principles</h2>
|
||||
|
||||
|
|
|
@ -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.
|
||||
The most notable changes are:
|
||||
|
||||
- the plugin class has been replaced as a function to be more idiomatic
|
||||
- the plugin name has been renamed to use camelcase
|
||||
- the options object has been extended to support additional routing options
|
||||
* the plugin class has been replaced as a function to be more idiomatic
|
||||
* the plugin name has been renamed to use camelcase
|
||||
* the options object has been extended to support additional routing options
|
||||
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
The most notable changes are:
|
||||
|
||||
- the `apolloServer` function no longer exists and was replaced with `apolloExpress`.
|
||||
- `apolloExpress` no longer accepts shorthand type definitions
|
||||
- `apolloExpress` doesn't have the `resolvers`, `mocks` and `connectors` options.
|
||||
- `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
|
||||
- Apollo Server no longer accepts GET requests or parameters in the URL
|
||||
- `apolloExpress` no longer parses the HTTP body automatically
|
||||
|
||||
* the `apolloServer` function no longer exists and was replaced with `apolloExpress`.
|
||||
* `apolloExpress` no longer accepts shorthand type definitions
|
||||
* `apolloExpress` doesn't have the `resolvers`, `mocks` and `connectors` options.
|
||||
* `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
|
||||
* Apollo Server no longer accepts GET requests or parameters in the URL
|
||||
* `apolloExpress` no longer parses the HTTP body automatically
|
||||
|
||||
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
|
||||
replace the old `apolloServer` function.
|
||||
|
||||
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.
|
||||
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
|
||||
|
@ -38,22 +38,26 @@ const GRAPHQL_PORT = 8080;
|
|||
|
||||
const graphQLServer = express();
|
||||
|
||||
graphQLServer.use('/graphql', apolloServer({
|
||||
graphiql: true,
|
||||
schema: Schema,
|
||||
resolvers: Resolvers,
|
||||
connectors: Connectors,
|
||||
mocks: Mocks,
|
||||
}));
|
||||
graphQLServer.use(
|
||||
'/graphql',
|
||||
apolloServer({
|
||||
graphiql: true,
|
||||
schema: Schema,
|
||||
resolvers: Resolvers,
|
||||
connectors: Connectors,
|
||||
mocks: Mocks,
|
||||
}),
|
||||
);
|
||||
|
||||
graphQLServer.listen(GRAPHQL_PORT, () => console.log(
|
||||
`Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
|
||||
));
|
||||
graphQLServer.listen(GRAPHQL_PORT, () =>
|
||||
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:
|
||||
|
||||
|
||||
```js
|
||||
import express from 'express';
|
||||
|
||||
|
@ -67,8 +71,6 @@ import { apolloExpress, graphiqlExpress } from 'apollo-server';
|
|||
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
|
||||
|
||||
const GRAPHQL_PORT = 8080;
|
||||
|
||||
const graphQLServer = express();
|
||||
|
@ -86,16 +88,25 @@ addMockFunctionsToSchema({
|
|||
});
|
||||
|
||||
// `context` must be an object and can't be undefined when using connectors
|
||||
graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({
|
||||
schema: executableSchema,
|
||||
context: {}, //at least(!) an empty object
|
||||
}));
|
||||
graphQLServer.use(
|
||||
'/graphql',
|
||||
bodyParser.json(),
|
||||
apolloExpress({
|
||||
schema: executableSchema,
|
||||
context: {}, //at least(!) an empty object
|
||||
}),
|
||||
);
|
||||
|
||||
graphQLServer.use('/graphiql', graphiqlExpress({
|
||||
endpointURL: '/graphql',
|
||||
}));
|
||||
graphQLServer.use(
|
||||
'/graphiql',
|
||||
graphiqlExpress({
|
||||
endpointURL: '/graphql',
|
||||
}),
|
||||
);
|
||||
|
||||
graphQLServer.listen(GRAPHQL_PORT, () => console.log(
|
||||
`Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
|
||||
));
|
||||
graphQLServer.listen(GRAPHQL_PORT, () =>
|
||||
console.log(
|
||||
`Apollo Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`,
|
||||
),
|
||||
);
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
||||
```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 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>
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@ app.use(
|
|||
'/graphql',
|
||||
bodyParser.json(),
|
||||
graphqlExpress({
|
||||
schema: myGraphQLSchema
|
||||
schema: myGraphQLSchema,
|
||||
// other options here
|
||||
})
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
|
@ -34,11 +34,11 @@ app.use(
|
|||
return {
|
||||
schema: myGraphQLSchema,
|
||||
context: {
|
||||
value: req.body.something
|
||||
}
|
||||
value: req.body.something,
|
||||
},
|
||||
// other options here
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
|
@ -67,11 +67,11 @@ app.use(
|
|||
return {
|
||||
schema: myGraphQLSchema,
|
||||
context: {
|
||||
user: userForThisRequest
|
||||
}
|
||||
user: userForThisRequest,
|
||||
},
|
||||
// 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:
|
||||
|
||||
- [Express / Connect](./servers/express.html)
|
||||
- [Hapi](./servers/hapi.html)
|
||||
- [Koa](./servers/koa.html)
|
||||
* [Express / Connect](./servers/express.html)
|
||||
* [Hapi](./servers/hapi.html)
|
||||
* [Koa](./servers/koa.html)
|
||||
|
||||
And more are being added every day!
|
||||
|
|
|
@ -12,7 +12,5 @@
|
|||
"tag: internal": ":house: Internal"
|
||||
}
|
||||
},
|
||||
"packages": [
|
||||
"packages/*"
|
||||
]
|
||||
"packages": ["packages/*"]
|
||||
}
|
||||
|
|
31
package.json
31
package.json
|
@ -7,19 +7,33 @@
|
|||
},
|
||||
"scripts": {
|
||||
"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",
|
||||
"postinstall": "lerna bootstrap",
|
||||
"pretest": "npm run compile",
|
||||
"test": "npm run testonly --",
|
||||
"posttest": "npm run lint",
|
||||
"testonly": "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",
|
||||
"testonly":
|
||||
"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",
|
||||
"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",
|
||||
"postcoverage": "remap-istanbul --input coverage/coverage.raw.json --type lcovonly --output coverage/lcov.info",
|
||||
"release": "lerna publish"
|
||||
"postcoverage":
|
||||
"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": {
|
||||
"@types/chai": "4.1.0",
|
||||
|
@ -28,15 +42,18 @@
|
|||
"@types/sinon": "4.1.2",
|
||||
"chai": "4.1.2",
|
||||
"graphql": "0.11.7",
|
||||
"husky": "^0.14.3",
|
||||
"istanbul": "1.1.0-alpha.1",
|
||||
"lerna": "2.5.1",
|
||||
"lint-staged": "6.0.0",
|
||||
"mocha": "4.1.0",
|
||||
"npm-check-updates": "2.14.0",
|
||||
"prettier": "^1.9.2",
|
||||
"prettier-check": "^2.0.0",
|
||||
"remap-istanbul": "0.9.5",
|
||||
"sinon": "4.1.4",
|
||||
"supertest": "3.0.0",
|
||||
"supertest-as-promised": "4.0.2",
|
||||
"tslint": "5.7.0",
|
||||
"typescript": "2.6.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,8 @@ const Route = use('Route');
|
|||
Route.get(
|
||||
'/graphiql',
|
||||
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',
|
||||
graphiqlAdonis({
|
||||
endpointURL: '/graphql',
|
||||
passHeader: `'Authorization': 'Bearer lorem ipsum'`
|
||||
})
|
||||
passHeader: `'Authorization': 'Bearer lorem ipsum'`,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Adonis",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Adonis", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,7 +4,10 @@ import { setupResolver, Config } from '@adonisjs/sink';
|
|||
import { graphqlAdonis, graphiqlAdonis } from './adonisApollo';
|
||||
import { GraphQLOptions } from 'apollo-server-core';
|
||||
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');
|
||||
|
||||
|
@ -12,16 +15,12 @@ async function createApp(options: CreateAppOptions = {}) {
|
|||
ioc.restore();
|
||||
RouteStore.clear();
|
||||
options.graphqlOptions = options.graphqlOptions || { schema };
|
||||
const providers = [
|
||||
'@adonisjs/framework/providers/AppProvider',
|
||||
];
|
||||
const providers = ['@adonisjs/framework/providers/AppProvider'];
|
||||
if (!options.excludeParser) {
|
||||
providers.push('@adonisjs/bodyparser/providers/BodyParserProvider');
|
||||
}
|
||||
setupResolver();
|
||||
registrar
|
||||
.providers(providers)
|
||||
.register();
|
||||
registrar.providers(providers).register();
|
||||
ioc.bind('Adonis/Src/Config', () => {
|
||||
const config = new Config();
|
||||
config.set('app', {
|
||||
|
@ -41,13 +40,21 @@ async function createApp(options: CreateAppOptions = {}) {
|
|||
const Route = ioc.use('Adonis/Src/Route');
|
||||
const Server = ioc.use('Adonis/Src/Server');
|
||||
|
||||
Context.getter('request', function () {
|
||||
return new Request(this.req, this.res, ioc.use('Adonis/Src/Config'));
|
||||
}, true);
|
||||
Context.getter(
|
||||
'request',
|
||||
function() {
|
||||
return new Request(this.req, this.res, ioc.use('Adonis/Src/Config'));
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
Context.getter('response', function () {
|
||||
return new Response(this.req, this.res, ioc.use('Adonis/Src/Config'));
|
||||
}, true);
|
||||
Context.getter(
|
||||
'response',
|
||||
function() {
|
||||
return new Response(this.req, this.res, ioc.use('Adonis/Src/Config'));
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
Route.post('/graphql', graphqlAdonis(options.graphqlOptions));
|
||||
Route.get('/graphql', graphqlAdonis(options.graphqlOptions));
|
||||
|
@ -57,7 +64,7 @@ async function createApp(options: CreateAppOptions = {}) {
|
|||
if (!options.excludeParser) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -65,17 +72,20 @@ async function destroyApp(app) {
|
|||
if (!app || !app.close) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => app.close(resolve));
|
||||
await new Promise(resolve => app.close(resolve));
|
||||
}
|
||||
|
||||
describe('adonisApollo', () => {
|
||||
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() {
|
||||
expect(() => (<any>graphqlAdonis)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2');
|
||||
expect(() => (<any>graphqlAdonis)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
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';
|
||||
|
||||
export interface AdonisGraphQLOptionsFunction {
|
||||
|
@ -10,32 +14,41 @@ export interface AdonisHandler {
|
|||
(req: any, next): void;
|
||||
}
|
||||
|
||||
export function graphqlAdonis (options: GraphQLOptions | AdonisGraphQLOptionsFunction): AdonisHandler {
|
||||
export function graphqlAdonis(
|
||||
options: GraphQLOptions | AdonisGraphQLOptionsFunction,
|
||||
): AdonisHandler {
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`);
|
||||
throw new Error(
|
||||
`Apollo Server expects exactly one argument, got ${arguments.length}`,
|
||||
);
|
||||
}
|
||||
return (ctx: AdonisContext): Promise<void> => {
|
||||
const { request, response } = ctx;
|
||||
const method = request.method();
|
||||
const query = method === 'POST' ? request.post() : request.get();
|
||||
return runHttpQuery([ctx], {
|
||||
method, options, query,
|
||||
}).then(gqlResponse => {
|
||||
response.json(gqlResponse);
|
||||
}, (error: HttpQueryError) => {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
throw error;
|
||||
}
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach((header) => {
|
||||
response.header(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
response.status(error.statusCode).send(error.message);
|
||||
});
|
||||
method,
|
||||
options,
|
||||
query,
|
||||
}).then(
|
||||
gqlResponse => {
|
||||
response.json(gqlResponse);
|
||||
},
|
||||
(error: HttpQueryError) => {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
throw error;
|
||||
}
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
response.header(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
response.status(error.statusCode).send(error.message);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -43,15 +56,19 @@ export interface AdonisGraphiQLOptionsFunction {
|
|||
(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> => {
|
||||
const { request, response } = ctx;
|
||||
const query = request.get();
|
||||
return GraphiQL.resolveGraphiQLString(query, options, ctx)
|
||||
.then(graphiqlString => {
|
||||
return GraphiQL.resolveGraphiQLString(query, options, ctx).then(
|
||||
graphiqlString => {
|
||||
response.type('text/html').send(graphiqlString);
|
||||
}, (error: HttpQueryError) => {
|
||||
},
|
||||
(error: HttpQueryError) => {
|
||||
response.status(500).send(error.message);
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -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/)
|
||||
|
||||
|
||||
## Example:
|
||||
|
||||
```js
|
||||
const server = require("apollo-server-azure-functions");
|
||||
const graphqlTools = require("graphql-tools");
|
||||
const server = require('apollo-server-azure-functions');
|
||||
const graphqlTools = require('graphql-tools');
|
||||
|
||||
const typeDefs = `
|
||||
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 = {
|
||||
Query: {
|
||||
rands: () => rands,
|
||||
rand: (_, { id }) => rands.find(rand => rand.id === id)
|
||||
}
|
||||
rand: (_, { id }) => rands.find(rand => rand.id === id),
|
||||
},
|
||||
};
|
||||
|
||||
const schema = graphqlTools.makeExecutableSchema({
|
||||
typeDefs,
|
||||
resolvers
|
||||
resolvers,
|
||||
});
|
||||
|
||||
module.exports = function run(context, request) {
|
||||
if (request.method === "POST") {
|
||||
if (request.method === 'POST') {
|
||||
server.graphqlAzureFunctions({
|
||||
endpointURL: '/api/graphql'
|
||||
endpointURL: '/api/graphql',
|
||||
})(context, request);
|
||||
} else if (request.method === "GET") {
|
||||
} else if (request.method === 'GET') {
|
||||
return server.graphiqlAzureFunctions({
|
||||
endpointURL: '/api/graphql'
|
||||
endpointURL: '/api/graphql',
|
||||
})(context, request);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Azure",
|
||||
"Functions"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Azure", "Functions"],
|
||||
"author": "Ulrik Strid <ulrik.strid@outlook.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
AzureFunctionsHandler,
|
||||
graphqlAzureFunctions,
|
||||
graphiqlAzureFunctions,
|
||||
AzureFunctionsHandler,
|
||||
graphqlAzureFunctions,
|
||||
graphiqlAzureFunctions,
|
||||
} from './azureFunctionsApollo';
|
||||
import testSuite, {
|
||||
schema as Schema,
|
||||
CreateAppOptions,
|
||||
schema as Schema,
|
||||
CreateAppOptions,
|
||||
} from 'apollo-server-integration-testsuite';
|
||||
import { expect } from 'chai';
|
||||
import { GraphQLOptions } from 'apollo-server-core';
|
||||
|
@ -13,77 +13,77 @@ import 'mocha';
|
|||
import * as url from 'url';
|
||||
|
||||
function createFunction(options: CreateAppOptions = {}) {
|
||||
let route, callback, context;
|
||||
let handler: AzureFunctionsHandler;
|
||||
let route, callback, context;
|
||||
let handler: AzureFunctionsHandler;
|
||||
|
||||
options.graphqlOptions = options.graphqlOptions || { schema: Schema };
|
||||
if (options.graphiqlOptions) {
|
||||
route = '/graphiql';
|
||||
handler = graphiqlAzureFunctions(options.graphiqlOptions);
|
||||
} else {
|
||||
route = '/graphql';
|
||||
handler = graphqlAzureFunctions(options.graphqlOptions);
|
||||
options.graphqlOptions = options.graphqlOptions || { schema: Schema };
|
||||
if (options.graphiqlOptions) {
|
||||
route = '/graphiql';
|
||||
handler = graphiqlAzureFunctions(options.graphiqlOptions);
|
||||
} else {
|
||||
route = '/graphql';
|
||||
handler = graphqlAzureFunctions(options.graphqlOptions);
|
||||
}
|
||||
|
||||
return function(req, res) {
|
||||
if (!req.url.startsWith(route)) {
|
||||
res.statusCode = 404;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
return function(req, res) {
|
||||
if (!req.url.startsWith(route)) {
|
||||
res.statusCode = 404;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
let body = '';
|
||||
req.on('data', function(chunk) {
|
||||
body += chunk;
|
||||
});
|
||||
req.on('end', function() {
|
||||
let urlObject = url.parse(req.url, true);
|
||||
const request = {
|
||||
method: req.method,
|
||||
originalUrl: req.url,
|
||||
query: urlObject.query,
|
||||
headers: req.headers,
|
||||
body: body,
|
||||
rawbody: body,
|
||||
};
|
||||
|
||||
let body = '';
|
||||
req.on('data', function(chunk) {
|
||||
body += chunk;
|
||||
});
|
||||
req.on('end', function() {
|
||||
let urlObject = url.parse(req.url, true);
|
||||
const request = {
|
||||
method: req.method,
|
||||
originalUrl: req.url,
|
||||
query: urlObject.query,
|
||||
headers: req.headers,
|
||||
body: body,
|
||||
rawbody: body,
|
||||
};
|
||||
context = {
|
||||
done: function(error, result) {
|
||||
res.statusCode = result.status;
|
||||
for (let key in result.headers) {
|
||||
if (result.headers.hasOwnProperty(key)) {
|
||||
res.setHeader(key, result.headers[key]);
|
||||
}
|
||||
}
|
||||
|
||||
context = {
|
||||
done: function(error, result) {
|
||||
res.statusCode = result.status;
|
||||
for (let key in result.headers) {
|
||||
if (result.headers.hasOwnProperty(key)) {
|
||||
res.setHeader(key, result.headers[key]);
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
res.error = error;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
res.error = error;
|
||||
}
|
||||
res.write(result.body);
|
||||
res.end();
|
||||
},
|
||||
};
|
||||
|
||||
res.write(result.body);
|
||||
res.end();
|
||||
},
|
||||
};
|
||||
|
||||
handler(context, request);
|
||||
});
|
||||
};
|
||||
handler(context, request);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
describe('azureFunctionsApollo', () => {
|
||||
it('throws error if called without schema', function() {
|
||||
expect(() => graphqlAzureFunctions(undefined as GraphQLOptions)).to.throw(
|
||||
'Apollo Server requires options.',
|
||||
);
|
||||
});
|
||||
it('throws error if called without schema', function() {
|
||||
expect(() => graphqlAzureFunctions(undefined as GraphQLOptions)).to.throw(
|
||||
'Apollo Server requires options.',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if called with more than one argument', function() {
|
||||
expect(() => (<any>graphqlAzureFunctions)({}, {})).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
it('throws an error if called with more than one argument', function() {
|
||||
expect(() => (<any>graphqlAzureFunctions)({}, {})).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration:Azure Functions', () => {
|
||||
testSuite(createFunction);
|
||||
testSuite(createFunction);
|
||||
});
|
||||
|
|
|
@ -1,87 +1,87 @@
|
|||
import {
|
||||
IHttpContext,
|
||||
IFunctionRequest,
|
||||
HttpStatusCodes,
|
||||
IHttpContext,
|
||||
IFunctionRequest,
|
||||
HttpStatusCodes,
|
||||
} from 'azure-functions-typescript';
|
||||
import { GraphQLOptions, runHttpQuery } from 'apollo-server-core';
|
||||
import * as GraphiQL from 'apollo-server-module-graphiql';
|
||||
|
||||
export interface AzureFunctionsGraphQLOptionsFunction {
|
||||
(context: IHttpContext): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
(context: IHttpContext): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
}
|
||||
|
||||
export interface AzureFunctionsHandler {
|
||||
(context: IHttpContext, request: IFunctionRequest): void;
|
||||
(context: IHttpContext, request: IFunctionRequest): void;
|
||||
}
|
||||
|
||||
export interface IHeaders {
|
||||
'content-type'?: string;
|
||||
'content-length'?: HttpStatusCodes | number;
|
||||
'content-disposition'?: string;
|
||||
'content-encoding'?: string;
|
||||
'content-language'?: string;
|
||||
'content-range'?: string;
|
||||
'content-location'?: string;
|
||||
'content-md5'?: Buffer;
|
||||
'expires'?: Date;
|
||||
'last-modified'?: Date;
|
||||
[header: string]: any;
|
||||
'content-type'?: string;
|
||||
'content-length'?: HttpStatusCodes | number;
|
||||
'content-disposition'?: string;
|
||||
'content-encoding'?: string;
|
||||
'content-language'?: string;
|
||||
'content-range'?: string;
|
||||
'content-location'?: string;
|
||||
'content-md5'?: Buffer;
|
||||
expires?: Date;
|
||||
'last-modified'?: Date;
|
||||
[header: string]: any;
|
||||
}
|
||||
|
||||
export interface AzureFunctionsGraphiQLOptionsFunction {
|
||||
(context: IHttpContext, request: IFunctionRequest):
|
||||
| GraphiQL.GraphiQLData
|
||||
| Promise<GraphiQL.GraphiQLData>;
|
||||
(context: IHttpContext, request: IFunctionRequest):
|
||||
| GraphiQL.GraphiQLData
|
||||
| Promise<GraphiQL.GraphiQLData>;
|
||||
}
|
||||
|
||||
export function graphqlAzureFunctions(
|
||||
options: GraphQLOptions | AzureFunctionsGraphQLOptionsFunction,
|
||||
options: GraphQLOptions | AzureFunctionsGraphQLOptionsFunction,
|
||||
): AzureFunctionsHandler {
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(
|
||||
`Apollo Server expects exactly one argument, got ${arguments.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (httpContext: IHttpContext, request: IFunctionRequest) => {
|
||||
const queryRequest = {
|
||||
method: request.method,
|
||||
options: options,
|
||||
query: request.method === 'POST' ? request.body : request.query,
|
||||
};
|
||||
|
||||
if (queryRequest.query && typeof queryRequest.query === 'string') {
|
||||
queryRequest.query = JSON.parse(queryRequest.query);
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(
|
||||
`Apollo Server expects exactly one argument, got ${arguments.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (httpContext: IHttpContext, request: IFunctionRequest) => {
|
||||
const queryRequest = {
|
||||
method: request.method,
|
||||
options: options,
|
||||
query: request.method === 'POST' ? request.body : request.query,
|
||||
return runHttpQuery([httpContext, request], queryRequest)
|
||||
.then(gqlResponse => {
|
||||
const result = {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: gqlResponse,
|
||||
};
|
||||
|
||||
if (queryRequest.query && typeof queryRequest.query === 'string') {
|
||||
queryRequest.query = JSON.parse(queryRequest.query);
|
||||
}
|
||||
httpContext.res = result;
|
||||
|
||||
return runHttpQuery([httpContext, request], queryRequest)
|
||||
.then(gqlResponse => {
|
||||
const result = {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: gqlResponse,
|
||||
};
|
||||
httpContext.done(null, result);
|
||||
})
|
||||
.catch(error => {
|
||||
const result = {
|
||||
status: error.statusCode,
|
||||
headers: error.headers,
|
||||
body: error.message,
|
||||
};
|
||||
|
||||
httpContext.res = result;
|
||||
httpContext.res = result;
|
||||
|
||||
httpContext.done(null, result);
|
||||
})
|
||||
.catch(error => {
|
||||
const result = {
|
||||
status: error.statusCode,
|
||||
headers: error.headers,
|
||||
body: error.message,
|
||||
};
|
||||
|
||||
httpContext.res = result;
|
||||
|
||||
httpContext.done(null, result);
|
||||
});
|
||||
};
|
||||
httpContext.done(null, result);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/* This Azure Functions Handler returns the html for the GraphiQL interactive query UI
|
||||
|
@ -96,31 +96,31 @@ export function graphqlAzureFunctions(
|
|||
*/
|
||||
|
||||
export function graphiqlAzureFunctions(
|
||||
options: GraphiQL.GraphiQLData | AzureFunctionsGraphiQLOptionsFunction,
|
||||
options: GraphiQL.GraphiQLData | AzureFunctionsGraphiQLOptionsFunction,
|
||||
) {
|
||||
return (httpContext: IHttpContext, request: IFunctionRequest) => {
|
||||
const query = request.query;
|
||||
return (httpContext: IHttpContext, request: IFunctionRequest) => {
|
||||
const query = request.query;
|
||||
|
||||
GraphiQL.resolveGraphiQLString(query, options, httpContext, request).then(
|
||||
graphiqlString => {
|
||||
httpContext.res = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
body: graphiqlString,
|
||||
};
|
||||
GraphiQL.resolveGraphiQLString(query, options, httpContext, request).then(
|
||||
graphiqlString => {
|
||||
httpContext.res = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
body: graphiqlString,
|
||||
};
|
||||
|
||||
httpContext.done(null, httpContext.res);
|
||||
},
|
||||
error => {
|
||||
httpContext.res = {
|
||||
status: 500,
|
||||
body: error.message,
|
||||
};
|
||||
httpContext.done(null, httpContext.res);
|
||||
},
|
||||
error => {
|
||||
httpContext.res = {
|
||||
status: 500,
|
||||
body: error.message,
|
||||
};
|
||||
|
||||
httpContext.done(null, httpContext.res);
|
||||
},
|
||||
);
|
||||
};
|
||||
httpContext.done(null, httpContext.res);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,15 +3,8 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"types": [
|
||||
"@types/node"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"types": ["@types/node"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -10,14 +10,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { GraphQLSchema, ValidationContext, GraphQLFieldResolver } from 'graphql';
|
||||
import {
|
||||
GraphQLSchema,
|
||||
ValidationContext,
|
||||
GraphQLFieldResolver,
|
||||
} from 'graphql';
|
||||
import { LogFunction } from './runQuery';
|
||||
import { GraphQLExtension } from 'graphql-extensions';
|
||||
|
||||
|
@ -34,7 +38,10 @@ export interface 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)) {
|
||||
try {
|
||||
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';
|
||||
}
|
||||
|
|
|
@ -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 { default as GraphQLOptions, resolveGraphqlOptions } from './graphqlOptions';
|
||||
|
||||
export {
|
||||
default as GraphQLOptions,
|
||||
resolveGraphqlOptions,
|
||||
} from './graphqlOptions';
|
||||
|
|
|
@ -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 { default as GraphQLOptions, resolveGraphqlOptions } from './graphqlOptions';
|
||||
import {
|
||||
default as GraphQLOptions,
|
||||
resolveGraphqlOptions,
|
||||
} from './graphqlOptions';
|
||||
|
||||
export interface HttpQueryRequest {
|
||||
method: string;
|
||||
|
@ -13,7 +22,12 @@ export class HttpQueryError extends Error {
|
|||
public isGraphQLError: boolean;
|
||||
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);
|
||||
this.name = 'HttpQueryError';
|
||||
this.statusCode = statusCode;
|
||||
|
@ -31,39 +45,53 @@ function isFunction(arg: any): arg is 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 optionsObject: GraphQLOptions;
|
||||
|
||||
try {
|
||||
optionsObject = await resolveGraphqlOptions(request.options, ...handlerArguments);
|
||||
optionsObject = await resolveGraphqlOptions(
|
||||
request.options,
|
||||
...handlerArguments,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new HttpQueryError(500, e.message);
|
||||
}
|
||||
const formatErrorFn = optionsObject.formatError || formatError;
|
||||
let requestPayload;
|
||||
|
||||
switch ( request.method ) {
|
||||
switch (request.method) {
|
||||
case 'POST':
|
||||
if ( !request.query || (Object.keys(request.query).length === 0) ) {
|
||||
throw new HttpQueryError(500, 'POST body missing. Did you forget use body-parser middleware?');
|
||||
if (!request.query || Object.keys(request.query).length === 0) {
|
||||
throw new HttpQueryError(
|
||||
500,
|
||||
'POST body missing. Did you forget use body-parser middleware?',
|
||||
);
|
||||
}
|
||||
|
||||
requestPayload = request.query;
|
||||
break;
|
||||
case 'GET':
|
||||
if ( !request.query || (Object.keys(request.query).length === 0) ) {
|
||||
throw new HttpQueryError(400, 'GET query missing.');
|
||||
}
|
||||
case 'GET':
|
||||
if (!request.query || Object.keys(request.query).length === 0) {
|
||||
throw new HttpQueryError(400, 'GET query missing.');
|
||||
}
|
||||
|
||||
isGetRequest = true;
|
||||
requestPayload = request.query;
|
||||
break;
|
||||
isGetRequest = true;
|
||||
requestPayload = request.query;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new HttpQueryError(405, 'Apollo Server supports only GET/POST requests.', false, {
|
||||
'Allow': 'GET, POST',
|
||||
});
|
||||
default:
|
||||
throw new HttpQueryError(
|
||||
405,
|
||||
'Apollo Server supports only GET/POST requests.',
|
||||
false,
|
||||
{
|
||||
Allow: 'GET, POST',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let isBatch = true;
|
||||
|
@ -77,16 +105,21 @@ export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQu
|
|||
const requests: Array<ExecutionResult> = requestPayload.map(requestParams => {
|
||||
try {
|
||||
let query = requestParams.query;
|
||||
if ( isGetRequest ) {
|
||||
if (isGetRequest) {
|
||||
if (typeof query === 'string') {
|
||||
// preparse the query incase of GET so we can assert the operation.
|
||||
query = parse(query);
|
||||
}
|
||||
|
||||
if ( ! isQueryOperation(query, requestParams.operationName) ) {
|
||||
throw new HttpQueryError(405, `GET supports only query operation`, false, {
|
||||
'Allow': 'POST',
|
||||
});
|
||||
if (!isQueryOperation(query, requestParams.operationName)) {
|
||||
throw new HttpQueryError(
|
||||
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)) {
|
||||
context = context();
|
||||
} else if (isBatch) {
|
||||
context = Object.assign(Object.create(Object.getPrototypeOf(context)), context);
|
||||
context = Object.assign(
|
||||
Object.create(Object.getPrototypeOf(context)),
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
let params = {
|
||||
|
@ -133,7 +169,7 @@ export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQu
|
|||
} catch (e) {
|
||||
// Populate any HttpQueryError to our handler which should
|
||||
// convert it to Http Error.
|
||||
if ( e.name === 'HttpQueryError' ) {
|
||||
if (e.name === 'HttpQueryError') {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,19 +4,15 @@ import { stub } from 'sinon';
|
|||
import 'mocha';
|
||||
|
||||
import {
|
||||
GraphQLSchema,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
parse,
|
||||
GraphQLSchema,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
parse,
|
||||
} from 'graphql';
|
||||
|
||||
import {
|
||||
runQuery,
|
||||
LogAction,
|
||||
LogStep,
|
||||
} from './runQuery';
|
||||
import { runQuery, LogAction, LogStep } from './runQuery';
|
||||
|
||||
// Make the global Promise constructor Fiber-aware to simulate a Meteor
|
||||
// environment.
|
||||
|
@ -25,102 +21,100 @@ import Fiber = require('fibers');
|
|||
makeCompatible(Promise, Fiber);
|
||||
|
||||
const queryType = new GraphQLObjectType({
|
||||
name: 'QueryType',
|
||||
fields: {
|
||||
testString: {
|
||||
name: 'QueryType',
|
||||
fields: {
|
||||
testString: {
|
||||
type: GraphQLString,
|
||||
resolve() {
|
||||
return 'it works';
|
||||
},
|
||||
},
|
||||
testObject: {
|
||||
type: new GraphQLObjectType({
|
||||
name: 'TestObject',
|
||||
fields: {
|
||||
testString: {
|
||||
type: GraphQLString,
|
||||
resolve() {
|
||||
return 'it works';
|
||||
},
|
||||
},
|
||||
testObject: {
|
||||
type: new GraphQLObjectType({
|
||||
name: 'TestObject',
|
||||
fields: {
|
||||
testString: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
},
|
||||
}),
|
||||
resolve() {
|
||||
return {
|
||||
testString: 'a very test string',
|
||||
};
|
||||
},
|
||||
},
|
||||
testRootValue: {
|
||||
type: GraphQLString,
|
||||
resolve(root) {
|
||||
return root + ' works';
|
||||
},
|
||||
},
|
||||
testContextValue: {
|
||||
type: GraphQLString,
|
||||
resolve(root, args, context) {
|
||||
return context + ' works';
|
||||
},
|
||||
},
|
||||
testArgumentValue: {
|
||||
type: GraphQLInt,
|
||||
resolve(root, args, context) {
|
||||
return args['base'] + 5;
|
||||
},
|
||||
args: {
|
||||
base: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
},
|
||||
},
|
||||
testAwaitedValue: {
|
||||
type: GraphQLString,
|
||||
resolve(root) {
|
||||
// Calling Promise.await is legal here even though this is
|
||||
// not an async function, because we are guaranteed to be
|
||||
// running in a Fiber.
|
||||
return 'it ' + (<any>Promise).await('works');
|
||||
},
|
||||
},
|
||||
testError: {
|
||||
type: GraphQLString,
|
||||
resolve() {
|
||||
throw new Error('Secret error message');
|
||||
},
|
||||
},
|
||||
}),
|
||||
resolve() {
|
||||
return {
|
||||
testString: 'a very test string',
|
||||
};
|
||||
},
|
||||
},
|
||||
testRootValue: {
|
||||
type: GraphQLString,
|
||||
resolve(root) {
|
||||
return root + ' works';
|
||||
},
|
||||
},
|
||||
testContextValue: {
|
||||
type: GraphQLString,
|
||||
resolve(root, args, context) {
|
||||
return context + ' works';
|
||||
},
|
||||
},
|
||||
testArgumentValue: {
|
||||
type: GraphQLInt,
|
||||
resolve(root, args, context) {
|
||||
return args['base'] + 5;
|
||||
},
|
||||
args: {
|
||||
base: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
},
|
||||
},
|
||||
testAwaitedValue: {
|
||||
type: GraphQLString,
|
||||
resolve(root) {
|
||||
// Calling Promise.await is legal here even though this is
|
||||
// not an async function, because we are guaranteed to be
|
||||
// running in a Fiber.
|
||||
return 'it ' + (<any>Promise).await('works');
|
||||
},
|
||||
},
|
||||
testError: {
|
||||
type: GraphQLString,
|
||||
resolve() {
|
||||
throw new Error('Secret error message');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const schema = new GraphQLSchema({
|
||||
query: queryType,
|
||||
query: queryType,
|
||||
});
|
||||
|
||||
describe('runQuery', () => {
|
||||
it('returns the right result when query is a string', () => {
|
||||
const query = `{ testString }`;
|
||||
const expected = { testString: 'it works' };
|
||||
return runQuery({ schema, query: query })
|
||||
.then((res) => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
const query = `{ testString }`;
|
||||
const expected = { testString: 'it works' };
|
||||
return runQuery({ schema, query: query }).then(res => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the right result when query is a document', () => {
|
||||
const query = parse(`{ testString }`);
|
||||
const expected = { testString: 'it works' };
|
||||
return runQuery({ schema, query: query })
|
||||
.then((res) => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
const query = parse(`{ testString }`);
|
||||
const expected = { testString: 'it works' };
|
||||
return runQuery({ schema, query: query }).then(res => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a syntax error if the query string contains one', () => {
|
||||
const query = `query { test `;
|
||||
const expected = /Syntax Error GraphQL/;
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
variables: { base: 1 },
|
||||
}).then((res) => {
|
||||
expect(res.data).to.be.undefined;
|
||||
expect(res.errors.length).to.equal(1);
|
||||
return expect(res.errors[0].message).to.match(expected);
|
||||
schema,
|
||||
query: query,
|
||||
variables: { base: 1 },
|
||||
}).then(res => {
|
||||
expect(res.data).to.be.undefined;
|
||||
expect(res.errors.length).to.equal(1);
|
||||
return expect(res.errors[0].message).to.match(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -129,13 +123,13 @@ describe('runQuery', () => {
|
|||
const expected = /at resolveFieldValueOrError/;
|
||||
const logStub = stub(console, 'error');
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
debug: true,
|
||||
}).then((res) => {
|
||||
logStub.restore();
|
||||
expect(logStub.callCount).to.equal(1);
|
||||
return expect(logStub.getCall(0).args[0]).to.match(expected);
|
||||
schema,
|
||||
query: query,
|
||||
debug: true,
|
||||
}).then(res => {
|
||||
logStub.restore();
|
||||
expect(logStub.callCount).to.equal(1);
|
||||
return expect(logStub.getCall(0).args[0]).to.match(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -143,145 +137,220 @@ describe('runQuery', () => {
|
|||
const query = `query { testError }`;
|
||||
const logStub = stub(console, 'error');
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
debug: false,
|
||||
}).then((res) => {
|
||||
logStub.restore();
|
||||
return expect(logStub.callCount).to.equal(0);
|
||||
schema,
|
||||
query: query,
|
||||
debug: false,
|
||||
}).then(res => {
|
||||
logStub.restore();
|
||||
return expect(logStub.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a validation error if the query string does not pass validation', () => {
|
||||
const query = `query TestVar($base: String){ testArgumentValue(base: $base) }`;
|
||||
const expected = 'Variable "$base" of type "String" used in position expecting type "Int!".';
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
variables: { base: 1 },
|
||||
}).then((res) => {
|
||||
expect(res.data).to.be.undefined;
|
||||
expect(res.errors.length).to.equal(1);
|
||||
return expect(res.errors[0].message).to.deep.equal(expected);
|
||||
});
|
||||
const query = `query TestVar($base: String){ testArgumentValue(base: $base) }`;
|
||||
const expected =
|
||||
'Variable "$base" of type "String" used in position expecting type "Int!".';
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
variables: { base: 1 },
|
||||
}).then(res => {
|
||||
expect(res.data).to.be.undefined;
|
||||
expect(res.errors.length).to.equal(1);
|
||||
return expect(res.errors[0].message).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly passes in the rootValue', () => {
|
||||
const query = `{ testRootValue }`;
|
||||
const expected = { testRootValue: 'it also works' };
|
||||
return runQuery({ schema, query: query, rootValue: 'it also' })
|
||||
.then((res) => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
const query = `{ testRootValue }`;
|
||||
const expected = { testRootValue: 'it also works' };
|
||||
return runQuery({ schema, query: query, rootValue: 'it also' }).then(
|
||||
res => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly passes in the context', () => {
|
||||
const query = `{ testContextValue }`;
|
||||
const expected = { testContextValue: 'it still works' };
|
||||
return runQuery({ schema, query: query, context: 'it still' })
|
||||
.then((res) => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
const query = `{ testContextValue }`;
|
||||
const expected = { testContextValue: 'it still works' };
|
||||
return runQuery({ schema, query: query, context: 'it still' }).then(res => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes the options to formatResponse', () => {
|
||||
const query = `{ testContextValue }`;
|
||||
const expected = { testContextValue: 'it still works' };
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
context: 'it still',
|
||||
formatResponse: (response, { context }) => {
|
||||
response['extensions'] = context;
|
||||
return response;
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.data).to.deep.equal(expected);
|
||||
return expect(res['extensions']).to.equal('it still');
|
||||
});
|
||||
const query = `{ testContextValue }`;
|
||||
const expected = { testContextValue: 'it still works' };
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
context: 'it still',
|
||||
formatResponse: (response, { context }) => {
|
||||
response['extensions'] = context;
|
||||
return response;
|
||||
},
|
||||
}).then(res => {
|
||||
expect(res.data).to.deep.equal(expected);
|
||||
return expect(res['extensions']).to.equal('it still');
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly passes in variables (and arguments)', () => {
|
||||
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
|
||||
const expected = { testArgumentValue: 6 };
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
variables: { base: 1 },
|
||||
}).then((res) => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
|
||||
const expected = { testArgumentValue: 6 };
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
variables: { base: 1 },
|
||||
}).then(res => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if there are missing variables', () => {
|
||||
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
|
||||
const expected = 'Variable "$base" of required type "Int!" was not provided.';
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
}).then((res) => {
|
||||
return expect(res.errors[0].message).to.deep.equal(expected);
|
||||
});
|
||||
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
|
||||
const expected =
|
||||
'Variable "$base" of required type "Int!" was not provided.';
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
}).then(res => {
|
||||
return expect(res.errors[0].message).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('supports yielding resolver functions', () => {
|
||||
return runQuery({
|
||||
schema,
|
||||
query: `{ testAwaitedValue }`,
|
||||
}).then((res) => {
|
||||
expect(res.data).to.deep.equal({
|
||||
testAwaitedValue: 'it works',
|
||||
});
|
||||
});
|
||||
it('supports yielding resolver functions', () => {
|
||||
return runQuery({
|
||||
schema,
|
||||
query: `{ testAwaitedValue }`,
|
||||
}).then(res => {
|
||||
expect(res.data).to.deep.equal({
|
||||
testAwaitedValue: 'it works',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('runs the correct operation when operationName is specified', () => {
|
||||
const query = `
|
||||
it('runs the correct operation when operationName is specified', () => {
|
||||
const query = `
|
||||
query Q1 {
|
||||
testString
|
||||
}
|
||||
query Q2 {
|
||||
testRootValue
|
||||
}`;
|
||||
const expected = {
|
||||
testString: 'it works',
|
||||
};
|
||||
return runQuery({ schema, query: query, operationName: 'Q1' })
|
||||
.then((res) => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
const expected = {
|
||||
testString: 'it works',
|
||||
};
|
||||
return runQuery({ schema, query: query, operationName: 'Q1' }).then(res => {
|
||||
return expect(res.data).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls logFunction', () => {
|
||||
const query = `
|
||||
it('calls logFunction', () => {
|
||||
const query = `
|
||||
query Q1 {
|
||||
testString
|
||||
}`;
|
||||
const logs = [];
|
||||
const logFn = (obj) => logs.push(obj);
|
||||
const expected = {
|
||||
testString: 'it works',
|
||||
};
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
variables: { test: 123 },
|
||||
logFunction: logFn,
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.data).to.deep.equal(expected);
|
||||
expect(logs.length).to.equals(11);
|
||||
expect(logs[0]).to.deep.equals({action: LogAction.request, step: LogStep.start});
|
||||
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});
|
||||
});
|
||||
const logs = [];
|
||||
const logFn = obj => logs.push(obj);
|
||||
const expected = {
|
||||
testString: 'it works',
|
||||
};
|
||||
return runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
variables: { test: 123 },
|
||||
logFunction: logFn,
|
||||
}).then(res => {
|
||||
expect(res.data).to.deep.equal(expected);
|
||||
expect(logs.length).to.equals(11);
|
||||
expect(logs[0]).to.deep.equals({
|
||||
action: LogAction.request,
|
||||
step: LogStep.start,
|
||||
});
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('uses custom field resolver', async () => {
|
||||
const query = `
|
||||
query Q1 {
|
||||
testObject {
|
||||
testString
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result1 = await runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
});
|
||||
|
||||
it('uses custom field resolver', async () => {
|
||||
expect(result1.data).to.deep.equal({
|
||||
testObject: {
|
||||
testString: 'a very test string',
|
||||
},
|
||||
});
|
||||
|
||||
const result2 = await runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
fieldResolver: () => 'a very testful field resolver string',
|
||||
});
|
||||
|
||||
expect(result2.data).to.deep.equal({
|
||||
testObject: {
|
||||
testString: 'a very testful field resolver string',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('async_hooks', () => {
|
||||
let asyncHooks;
|
||||
let asyncHook;
|
||||
const ids: number[] = [];
|
||||
|
||||
try {
|
||||
asyncHooks = require('async_hooks');
|
||||
} catch (err) {
|
||||
return; // async_hooks not present, give up
|
||||
}
|
||||
|
||||
before(() => {
|
||||
asyncHook = asyncHooks.createHook({ init: asyncId => ids.push(asyncId) });
|
||||
asyncHook.enable();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
asyncHook.disable();
|
||||
});
|
||||
|
||||
it('does not break async_hook call stack', async () => {
|
||||
const query = `
|
||||
query Q1 {
|
||||
testObject {
|
||||
|
@ -290,73 +359,18 @@ describe('runQuery', () => {
|
|||
}
|
||||
`;
|
||||
|
||||
const result1 = await runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
await runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
});
|
||||
|
||||
expect(result1.data).to.deep.equal({
|
||||
testObject: {
|
||||
testString: 'a very test string',
|
||||
},
|
||||
});
|
||||
|
||||
const result2 = await runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
fieldResolver: () => 'a very testful field resolver string',
|
||||
});
|
||||
|
||||
expect(result2.data).to.deep.equal({
|
||||
testObject: {
|
||||
testString: 'a very testful field resolver string',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('async_hooks', () => {
|
||||
let asyncHooks;
|
||||
let asyncHook;
|
||||
const ids: number[] = [];
|
||||
|
||||
try {
|
||||
asyncHooks = require('async_hooks');
|
||||
} catch (err) {
|
||||
return; // async_hooks not present, give up
|
||||
// this is the only async process so we expect the async ids to be a sequence
|
||||
ids.forEach((id, i) => {
|
||||
if (i > 0) {
|
||||
expect(id).to.equal(ids[i - 1] + 1);
|
||||
}
|
||||
|
||||
before(() => {
|
||||
asyncHook = asyncHooks.createHook({ init: (asyncId) => ids.push(asyncId) });
|
||||
asyncHook.enable();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
asyncHook.disable();
|
||||
});
|
||||
|
||||
it('does not break async_hook call stack', async () => {
|
||||
const query = `
|
||||
query Q1 {
|
||||
testObject {
|
||||
testString
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
await runQuery({
|
||||
schema,
|
||||
query: query,
|
||||
operationName: 'Q1',
|
||||
});
|
||||
|
||||
// this is the only async process so we expect the async ids to be a sequence
|
||||
ids.forEach((id, i) => {
|
||||
if (i > 0) {
|
||||
expect(id).to.equal(ids[i - 1] + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import {
|
||||
GraphQLSchema,
|
||||
GraphQLFieldResolver,
|
||||
ExecutionResult,
|
||||
DocumentNode,
|
||||
parse,
|
||||
print,
|
||||
validate,
|
||||
execute,
|
||||
GraphQLError,
|
||||
formatError,
|
||||
specifiedRules,
|
||||
ValidationContext,
|
||||
GraphQLSchema,
|
||||
GraphQLFieldResolver,
|
||||
ExecutionResult,
|
||||
DocumentNode,
|
||||
parse,
|
||||
print,
|
||||
validate,
|
||||
execute,
|
||||
GraphQLError,
|
||||
formatError,
|
||||
specifiedRules,
|
||||
ValidationContext,
|
||||
} from 'graphql';
|
||||
|
||||
import { enableGraphQLExtensions, GraphQLExtension, GraphQLExtensionStack } from 'graphql-extensions';
|
||||
import {
|
||||
enableGraphQLExtensions,
|
||||
GraphQLExtension,
|
||||
GraphQLExtensionStack,
|
||||
} from 'graphql-extensions';
|
||||
import { TracingExtension } from 'apollo-tracing';
|
||||
import { CacheControlExtension } from 'apollo-cache-control';
|
||||
|
||||
|
@ -24,11 +28,16 @@ export interface GraphQLResponse {
|
|||
}
|
||||
|
||||
export enum LogAction {
|
||||
request, parse, validation, execute,
|
||||
request,
|
||||
parse,
|
||||
validation,
|
||||
execute,
|
||||
}
|
||||
|
||||
export enum LogStep {
|
||||
start, end, status,
|
||||
start,
|
||||
end,
|
||||
status,
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
|
@ -43,153 +52,178 @@ export interface LogFunction {
|
|||
}
|
||||
|
||||
export interface QueryOptions {
|
||||
schema: GraphQLSchema;
|
||||
query: string | DocumentNode;
|
||||
rootValue?: any;
|
||||
context?: any;
|
||||
variables?: { [key: string]: any };
|
||||
operationName?: string;
|
||||
logFunction?: LogFunction;
|
||||
validationRules?: Array<(context: ValidationContext) => any>;
|
||||
fieldResolver?: GraphQLFieldResolver<any, any>;
|
||||
// WARNING: these extra validation rules are only applied to queries
|
||||
// submitted as string, not those submitted as Document!
|
||||
schema: GraphQLSchema;
|
||||
query: string | DocumentNode;
|
||||
rootValue?: any;
|
||||
context?: any;
|
||||
variables?: { [key: string]: any };
|
||||
operationName?: string;
|
||||
logFunction?: LogFunction;
|
||||
validationRules?: Array<(context: ValidationContext) => any>;
|
||||
fieldResolver?: GraphQLFieldResolver<any, any>;
|
||||
// WARNING: these extra validation rules are only applied to queries
|
||||
// submitted as string, not those submitted as Document!
|
||||
|
||||
formatError?: Function;
|
||||
formatResponse?: Function;
|
||||
debug?: boolean;
|
||||
tracing?: boolean;
|
||||
cacheControl?: boolean;
|
||||
formatError?: Function;
|
||||
formatResponse?: Function;
|
||||
debug?: boolean;
|
||||
tracing?: boolean;
|
||||
cacheControl?: boolean;
|
||||
}
|
||||
|
||||
function runQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
||||
// Fiber-aware Promises run their .then callbacks in Fibers.
|
||||
return Promise.resolve().then(() => doRunQuery(options));
|
||||
// Fiber-aware Promises run their .then callbacks in Fibers.
|
||||
return Promise.resolve().then(() => doRunQuery(options));
|
||||
}
|
||||
|
||||
function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
||||
let documentAST: DocumentNode;
|
||||
let documentAST: DocumentNode;
|
||||
|
||||
const logFunction = options.logFunction || function(){ return null; };
|
||||
const debugDefault = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
|
||||
const debug = typeof options.debug !== 'undefined' ? options.debug : debugDefault;
|
||||
const logFunction =
|
||||
options.logFunction ||
|
||||
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 });
|
||||
|
||||
const context = options.context || {};
|
||||
let extensions = [];
|
||||
if (options.tracing) {
|
||||
extensions.push(TracingExtension);
|
||||
}
|
||||
if (options.cacheControl) {
|
||||
extensions.push(CacheControlExtension);
|
||||
}
|
||||
const extensionStack = extensions.length > 0 && new GraphQLExtensionStack(extensions);
|
||||
const context = options.context || {};
|
||||
let extensions = [];
|
||||
if (options.tracing) {
|
||||
extensions.push(TracingExtension);
|
||||
}
|
||||
if (options.cacheControl) {
|
||||
extensions.push(CacheControlExtension);
|
||||
}
|
||||
const extensionStack =
|
||||
extensions.length > 0 && new GraphQLExtensionStack(extensions);
|
||||
|
||||
if (extensionStack) {
|
||||
context._extensionStack = extensionStack;
|
||||
enableGraphQLExtensions(options.schema);
|
||||
if (extensionStack) {
|
||||
context._extensionStack = extensionStack;
|
||||
enableGraphQLExtensions(options.schema);
|
||||
|
||||
extensionStack.requestDidStart();
|
||||
}
|
||||
extensionStack.requestDidStart();
|
||||
}
|
||||
|
||||
function format(errors: Array<Error>): Array<Error> {
|
||||
return errors.map((error) => {
|
||||
if (options.formatError) {
|
||||
try {
|
||||
return options.formatError(error);
|
||||
} catch (err) {
|
||||
console.error('Error in formatError function:', err);
|
||||
const newError = new Error('Internal server error');
|
||||
return formatError(newError);
|
||||
}
|
||||
} else {
|
||||
return formatError(error);
|
||||
}
|
||||
}) as Array<Error>;
|
||||
}
|
||||
|
||||
function printStackTrace(error: Error) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
|
||||
const qry = typeof options.query === 'string' ? options.query : print(options.query);
|
||||
logFunction({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
|
||||
// XXX: This refers the operations-store flow.
|
||||
if (typeof options.query === 'string') {
|
||||
function format(errors: Array<Error>): Array<Error> {
|
||||
return errors.map(error => {
|
||||
if (options.formatError) {
|
||||
try {
|
||||
logFunction({action: LogAction.parse, step: LogStep.start});
|
||||
documentAST = parse(options.query as string);
|
||||
logFunction({action: LogAction.parse, step: LogStep.end});
|
||||
} catch (syntaxError) {
|
||||
logFunction({action: LogAction.parse, step: LogStep.end});
|
||||
return Promise.resolve({ errors: format([syntaxError]) });
|
||||
return options.formatError(error);
|
||||
} catch (err) {
|
||||
console.error('Error in formatError function:', err);
|
||||
const newError = new Error('Internal server error');
|
||||
return formatError(newError);
|
||||
}
|
||||
} else {
|
||||
documentAST = options.query as DocumentNode;
|
||||
}
|
||||
} else {
|
||||
return formatError(error);
|
||||
}
|
||||
}) as Array<Error>;
|
||||
}
|
||||
|
||||
let rules = specifiedRules;
|
||||
if (options.validationRules) {
|
||||
rules = rules.concat(options.validationRules);
|
||||
}
|
||||
logFunction({action: LogAction.validation, step: LogStep.start});
|
||||
const validationErrors = validate(options.schema, documentAST, rules);
|
||||
logFunction({action: LogAction.validation, step: LogStep.end});
|
||||
if (validationErrors.length) {
|
||||
return Promise.resolve({ errors: format(validationErrors) });
|
||||
}
|
||||
function printStackTrace(error: Error) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
|
||||
if (extensionStack) {
|
||||
extensionStack.executionDidStart();
|
||||
}
|
||||
const qry =
|
||||
typeof options.query === 'string' ? options.query : print(options.query);
|
||||
logFunction({
|
||||
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
|
||||
// XXX: This refers the operations-store flow.
|
||||
if (typeof options.query === 'string') {
|
||||
try {
|
||||
logFunction({action: LogAction.execute, step: LogStep.start});
|
||||
return Promise.resolve(execute(
|
||||
options.schema,
|
||||
documentAST,
|
||||
options.rootValue,
|
||||
context,
|
||||
options.variables,
|
||||
options.operationName,
|
||||
options.fieldResolver,
|
||||
)).then(result => {
|
||||
logFunction({action: LogAction.execute, step: LogStep.end});
|
||||
logFunction({action: LogAction.request, step: LogStep.end});
|
||||
|
||||
let response: GraphQLResponse = {
|
||||
data: result.data,
|
||||
};
|
||||
|
||||
if (result.errors) {
|
||||
response.errors = format(result.errors);
|
||||
if (debug) {
|
||||
result.errors.map(printStackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
if (extensionStack) {
|
||||
extensionStack.executionDidEnd();
|
||||
extensionStack.requestDidEnd();
|
||||
response.extensions = extensionStack.format();
|
||||
}
|
||||
|
||||
if (options.formatResponse) {
|
||||
response = options.formatResponse(response, options);
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
} catch (executionError) {
|
||||
logFunction({action: LogAction.execute, step: LogStep.end});
|
||||
logFunction({action: LogAction.request, step: LogStep.end});
|
||||
return Promise.resolve({ errors: format([executionError]) });
|
||||
logFunction({ action: LogAction.parse, step: LogStep.start });
|
||||
documentAST = parse(options.query as string);
|
||||
logFunction({ action: LogAction.parse, step: LogStep.end });
|
||||
} catch (syntaxError) {
|
||||
logFunction({ action: LogAction.parse, step: LogStep.end });
|
||||
return Promise.resolve({ errors: format([syntaxError]) });
|
||||
}
|
||||
} else {
|
||||
documentAST = options.query as DocumentNode;
|
||||
}
|
||||
|
||||
let rules = specifiedRules;
|
||||
if (options.validationRules) {
|
||||
rules = rules.concat(options.validationRules);
|
||||
}
|
||||
logFunction({ action: LogAction.validation, step: LogStep.start });
|
||||
const validationErrors = validate(options.schema, documentAST, rules);
|
||||
logFunction({ action: LogAction.validation, step: LogStep.end });
|
||||
if (validationErrors.length) {
|
||||
return Promise.resolve({ errors: format(validationErrors) });
|
||||
}
|
||||
|
||||
if (extensionStack) {
|
||||
extensionStack.executionDidStart();
|
||||
}
|
||||
|
||||
try {
|
||||
logFunction({ action: LogAction.execute, step: LogStep.start });
|
||||
return Promise.resolve(
|
||||
execute(
|
||||
options.schema,
|
||||
documentAST,
|
||||
options.rootValue,
|
||||
context,
|
||||
options.variables,
|
||||
options.operationName,
|
||||
options.fieldResolver,
|
||||
),
|
||||
).then(result => {
|
||||
logFunction({ action: LogAction.execute, step: LogStep.end });
|
||||
logFunction({ action: LogAction.request, step: LogStep.end });
|
||||
|
||||
let response: GraphQLResponse = {
|
||||
data: result.data,
|
||||
};
|
||||
|
||||
if (result.errors) {
|
||||
response.errors = format(result.errors);
|
||||
if (debug) {
|
||||
result.errors.map(printStackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
if (extensionStack) {
|
||||
extensionStack.executionDidEnd();
|
||||
extensionStack.requestDidEnd();
|
||||
response.extensions = extensionStack.format();
|
||||
}
|
||||
|
||||
if (options.formatResponse) {
|
||||
response = options.formatResponse(response, options);
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
} catch (executionError) {
|
||||
logFunction({ action: LogAction.execute, step: LogStep.end });
|
||||
logFunction({ action: LogAction.request, step: LogStep.end });
|
||||
return Promise.resolve({ errors: format([executionError]) });
|
||||
}
|
||||
}
|
||||
|
||||
export { runQuery };
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include" : [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* **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!
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"name": "apollo-server-express",
|
||||
"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",
|
||||
"scripts": {
|
||||
"compile": "tsc",
|
||||
|
@ -9,7 +10,8 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
GraphQLString,
|
||||
GraphQLScalarType,
|
||||
GraphQLError,
|
||||
BREAK
|
||||
BREAK,
|
||||
} from 'graphql';
|
||||
|
||||
const QueryRootType = new GraphQLObjectType({
|
||||
|
@ -46,14 +46,16 @@ const QueryRootType = new GraphQLObjectType({
|
|||
type: GraphQLString,
|
||||
args: {
|
||||
who: {
|
||||
type: GraphQLString
|
||||
}
|
||||
type: GraphQLString,
|
||||
},
|
||||
},
|
||||
resolve: (root, args) => 'Hello ' + (args['who'] || 'World')
|
||||
resolve: (root, args) => 'Hello ' + (args['who'] || 'World'),
|
||||
},
|
||||
thrower: {
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
resolve: () => { throw new Error('Throws!'); }
|
||||
resolve: () => {
|
||||
throw new Error('Throws!');
|
||||
},
|
||||
},
|
||||
custom: {
|
||||
type: GraphQLString,
|
||||
|
@ -69,14 +71,14 @@ const QueryRootType = new GraphQLObjectType({
|
|||
throw new Error('Something bad happened');
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
context: {
|
||||
type: GraphQLString,
|
||||
resolve: (obj, args, context) => context,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TestSchema = new GraphQLSchema({
|
||||
|
@ -86,15 +88,15 @@ const TestSchema = new GraphQLSchema({
|
|||
fields: {
|
||||
writeTest: {
|
||||
type: QueryRootType,
|
||||
resolve: () => ({})
|
||||
}
|
||||
}
|
||||
})
|
||||
resolve: () => ({}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
function catchError(p) {
|
||||
return p.then(
|
||||
(res) => {
|
||||
res => {
|
||||
// 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.
|
||||
if (res && res.error) {
|
||||
|
@ -107,18 +109,17 @@ function catchError(p) {
|
|||
throw new Error('Expected error to be instanceof Error.');
|
||||
}
|
||||
return error;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function promiseTo(fn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fn((error, result) => error ? reject(error) : resolve(result));
|
||||
fn((error, result) => (error ? reject(error) : resolve(result)));
|
||||
});
|
||||
}
|
||||
|
||||
describe('test harness', () => {
|
||||
|
||||
it('expects to catch errors', async () => {
|
||||
let caught;
|
||||
try {
|
||||
|
@ -136,7 +137,9 @@ describe('test harness', () => {
|
|||
} catch (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 () => {
|
||||
|
@ -155,26 +158,29 @@ describe('test harness', () => {
|
|||
}
|
||||
expect(caught).to.equal(rejectError);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const express = express4;
|
||||
const version = 'modern';
|
||||
describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
||||
describe('POST functionality', () => {
|
||||
|
||||
it('allows gzipped POST bodies', async () => {
|
||||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress(() => ({
|
||||
schema: TestSchema
|
||||
})));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress(() => ({
|
||||
schema: TestSchema,
|
||||
})),
|
||||
);
|
||||
|
||||
const data = { query: '{ test(who: "World") }' };
|
||||
const json = JSON.stringify(data);
|
||||
// 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)
|
||||
.post('/graphql')
|
||||
|
@ -185,8 +191,8 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
data: {
|
||||
test: 'Hello World'
|
||||
}
|
||||
test: 'Hello World',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -194,14 +200,19 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress(() => ({
|
||||
schema: TestSchema
|
||||
})));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress(() => ({
|
||||
schema: TestSchema,
|
||||
})),
|
||||
);
|
||||
|
||||
const data = { query: '{ test(who: "World") }' };
|
||||
const json = JSON.stringify(data);
|
||||
// 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)
|
||||
.post('/graphql')
|
||||
|
@ -212,8 +223,8 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
data: {
|
||||
test: 'Hello World'
|
||||
}
|
||||
test: 'Hello World',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -227,16 +238,16 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
name: 'UploadedFile',
|
||||
fields: {
|
||||
originalname: { type: GraphQLString },
|
||||
mimetype: { type: GraphQLString }
|
||||
}
|
||||
mimetype: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
const TestMutationSchema = new GraphQLSchema({
|
||||
query: new GraphQLObjectType({
|
||||
name: 'QueryRoot',
|
||||
fields: {
|
||||
test: { type: GraphQLString }
|
||||
}
|
||||
test: { type: GraphQLString },
|
||||
},
|
||||
}),
|
||||
mutation: new GraphQLObjectType({
|
||||
name: 'MutationRoot',
|
||||
|
@ -248,10 +259,10 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
// file directly, but presumably you might return a Promise
|
||||
// to go store the file somewhere first.
|
||||
return rootValue.request.file;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
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
|
||||
// be accessible from within Schema resolve functions.
|
||||
app.use('/graphql', graphqlExpress(req => {
|
||||
return {
|
||||
schema: TestMutationSchema,
|
||||
rootValue: { request: req }
|
||||
};
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress(req => {
|
||||
return {
|
||||
schema: TestMutationSchema,
|
||||
rootValue: { request: req },
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const req = request(app)
|
||||
.post('/graphql')
|
||||
.field('query', `mutation TestMutation {
|
||||
.field(
|
||||
'query',
|
||||
`mutation TestMutation {
|
||||
uploadFile { originalname, mimetype }
|
||||
}`)
|
||||
}`,
|
||||
)
|
||||
.attach('file', __filename);
|
||||
|
||||
return req.then((response) => {
|
||||
return req.then(response => {
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
data: {
|
||||
uploadFile: {
|
||||
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();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress({
|
||||
schema: TestSchema
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
|
@ -308,11 +328,13 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
expect(response.status).to.equal(200);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
data: null,
|
||||
errors: [ {
|
||||
message: 'Throws!',
|
||||
locations: [ { line: 1, column: 2 } ],
|
||||
path:["thrower"]
|
||||
} ]
|
||||
errors: [
|
||||
{
|
||||
message: 'Throws!',
|
||||
locations: [{ line: 1, column: 2 }],
|
||||
path: ['thrower'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -320,9 +342,12 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress({
|
||||
schema: TestSchema
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
|
@ -332,29 +357,38 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
|
||||
expect(response.status).to.equal(400);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
errors: [ {
|
||||
message: 'Cannot query field \"notExists\" on type \"QueryRoot\".',
|
||||
locations: [ { line: 1, column: 2 } ],
|
||||
} ]
|
||||
errors: [
|
||||
{
|
||||
message: 'Cannot query field "notExists" on type "QueryRoot".',
|
||||
locations: [{ line: 1, column: 2 }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('handles type validation (GET)', async () => {
|
||||
const app = express();
|
||||
|
||||
app.use('/graphql', require('connect-query')(), graphqlExpress({
|
||||
schema: TestSchema
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
require('connect-query')(),
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/graphql').query({ query: '{notExists}' });
|
||||
.get('/graphql')
|
||||
.query({ query: '{notExists}' });
|
||||
|
||||
expect(response.status).to.equal(400);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
errors: [ {
|
||||
message: 'Cannot query field \"notExists\" on type \"QueryRoot\".',
|
||||
locations: [ { line: 1, column: 2 } ],
|
||||
} ]
|
||||
errors: [
|
||||
{
|
||||
message: 'Cannot query field "notExists" on type "QueryRoot".',
|
||||
locations: [{ line: 1, column: 2 }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -362,9 +396,12 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress({
|
||||
schema: TestSchema
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
|
@ -379,12 +416,15 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress({
|
||||
schema: TestSchema,
|
||||
formatError(error) {
|
||||
return { message: 'Custom error format: ' + error.message };
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
formatError(error) {
|
||||
return { message: 'Custom error format: ' + error.message };
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
|
@ -395,9 +435,11 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
expect(response.status).to.equal(200);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
data: null,
|
||||
errors: [ {
|
||||
message: 'Custom error format: Throws!',
|
||||
} ]
|
||||
errors: [
|
||||
{
|
||||
message: 'Custom error format: Throws!',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -405,16 +447,19 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress({
|
||||
schema: TestSchema,
|
||||
formatError(error) {
|
||||
return {
|
||||
message: error.message,
|
||||
locations: error.locations,
|
||||
stack: 'Stack trace'
|
||||
};
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
formatError(error) {
|
||||
return {
|
||||
message: error.message,
|
||||
locations: error.locations,
|
||||
stack: 'Stack trace',
|
||||
};
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
|
@ -425,11 +470,13 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
expect(response.status).to.equal(200);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
data: null,
|
||||
errors: [ {
|
||||
message: 'Throws!',
|
||||
locations: [ { line: 1, column: 2 } ],
|
||||
stack: 'Stack trace',
|
||||
} ]
|
||||
errors: [
|
||||
{
|
||||
message: 'Throws!',
|
||||
locations: [{ line: 1, column: 2 }],
|
||||
stack: 'Stack trace',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -440,50 +487,55 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => {
|
|||
app.use('/graphql', graphqlExpress({ schema: TestSchema }));
|
||||
|
||||
const response = await request(app)
|
||||
.put('/graphql').query({ query: '{test}' });
|
||||
.put('/graphql')
|
||||
.query({ query: '{test}' });
|
||||
|
||||
expect(response.status).to.equal(405);
|
||||
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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom validation rules', () => {
|
||||
const AlwaysInvalidRule = function (context) {
|
||||
return {
|
||||
enter() {
|
||||
context.reportError(new GraphQLError(
|
||||
'AlwaysInvalidRule was really invalid!'
|
||||
));
|
||||
return BREAK;
|
||||
}
|
||||
};
|
||||
const AlwaysInvalidRule = function(context) {
|
||||
return {
|
||||
enter() {
|
||||
context.reportError(
|
||||
new GraphQLError('AlwaysInvalidRule was really invalid!'),
|
||||
);
|
||||
return BREAK;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
it('Do not execute a query if it do not pass the custom validation.', async() => {
|
||||
const app = express();
|
||||
it('Do not execute a query if it do not pass the custom validation.', async () => {
|
||||
const app = express();
|
||||
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', graphqlExpress({
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlExpress({
|
||||
schema: TestSchema,
|
||||
validationRules: [ AlwaysInvalidRule ],
|
||||
}));
|
||||
validationRules: [AlwaysInvalidRule],
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.send({
|
||||
query: '{thrower}',
|
||||
})
|
||||
|
||||
expect(response.status).to.equal(400);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
errors: [
|
||||
{
|
||||
message: 'AlwaysInvalidRule was really invalid!'
|
||||
},
|
||||
]
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.send({
|
||||
query: '{thrower}',
|
||||
});
|
||||
|
||||
expect(response.status).to.equal(400);
|
||||
expect(JSON.parse(response.text)).to.deep.equal({
|
||||
errors: [
|
||||
{
|
||||
message: 'AlwaysInvalidRule was really invalid!',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,10 @@ import * as bodyParser from 'body-parser';
|
|||
import { graphqlConnect, graphiqlConnect } from './connectApollo';
|
||||
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 = {}) {
|
||||
const app = connect();
|
||||
|
@ -12,11 +15,11 @@ function createConnectApp(options: CreateAppOptions = {}) {
|
|||
if (!options.excludeParser) {
|
||||
app.use('/graphql', bodyParser.json());
|
||||
}
|
||||
if (options.graphiqlOptions ) {
|
||||
app.use('/graphiql', graphiqlConnect( options.graphiqlOptions ));
|
||||
if (options.graphiqlOptions) {
|
||||
app.use('/graphiql', graphiqlConnect(options.graphiqlOptions));
|
||||
}
|
||||
app.use('/graphql', require('connect-query')());
|
||||
app.use('/graphql', graphqlConnect( options.graphqlOptions ));
|
||||
app.use('/graphql', graphqlConnect(options.graphqlOptions));
|
||||
return app;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import * as express from 'express';
|
||||
import * as bodyParser from 'body-parser';
|
||||
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 { GraphQLOptions } from 'apollo-server-core';
|
||||
import 'mocha';
|
||||
|
@ -13,22 +16,25 @@ function createApp(options: CreateAppOptions = {}) {
|
|||
if (!options.excludeParser) {
|
||||
app.use('/graphql', bodyParser.json());
|
||||
}
|
||||
if (options.graphiqlOptions ) {
|
||||
app.use('/graphiql', graphiqlExpress( options.graphiqlOptions ));
|
||||
if (options.graphiqlOptions) {
|
||||
app.use('/graphiql', graphiqlExpress(options.graphiqlOptions));
|
||||
}
|
||||
app.use('/graphql', require('connect-query')());
|
||||
app.use('/graphql', graphqlExpress( options.graphqlOptions ));
|
||||
app.use('/graphql', graphqlExpress(options.graphqlOptions));
|
||||
return app;
|
||||
}
|
||||
|
||||
describe('expressApollo', () => {
|
||||
it('throws error if called without schema', function(){
|
||||
expect(() => graphqlExpress(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.');
|
||||
it('throws error if called without schema', function() {
|
||||
expect(() => graphqlExpress(undefined as GraphQLOptions)).to.throw(
|
||||
'Apollo Server requires options.',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if called with more than one argument', function(){
|
||||
expect(() => (<any>graphqlExpress)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2');
|
||||
it('throws an error if called with more than one argument', function() {
|
||||
expect(() => (<any>graphqlExpress)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import * as express from 'express';
|
||||
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';
|
||||
|
||||
export interface ExpressGraphQLOptionsFunction {
|
||||
(req?: express.Request, res?: express.Response): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
(req?: express.Request, res?: express.Response):
|
||||
| GraphQLOptions
|
||||
| Promise<GraphQLOptions>;
|
||||
}
|
||||
|
||||
// Design principles:
|
||||
|
@ -16,14 +22,18 @@ export interface ExpressHandler {
|
|||
(req: express.Request, res: express.Response, next): void;
|
||||
}
|
||||
|
||||
export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFunction): ExpressHandler {
|
||||
export function graphqlExpress(
|
||||
options: GraphQLOptions | ExpressGraphQLOptionsFunction,
|
||||
): ExpressHandler {
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
// 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 => {
|
||||
|
@ -31,31 +41,36 @@ export function graphqlExpress(options: GraphQLOptions | ExpressGraphQLOptionsFu
|
|||
method: req.method,
|
||||
options: options,
|
||||
query: req.method === 'POST' ? req.body : req.query,
|
||||
}).then((gqlResponse) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Length', Buffer.byteLength(gqlResponse, 'utf8'));
|
||||
res.write(gqlResponse);
|
||||
res.end();
|
||||
}, (error: HttpQueryError) => {
|
||||
if ( 'HttpQueryError' !== error.name ) {
|
||||
return next(error);
|
||||
}
|
||||
}).then(
|
||||
gqlResponse => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Length', Buffer.byteLength(gqlResponse, 'utf8'));
|
||||
res.write(gqlResponse);
|
||||
res.end();
|
||||
},
|
||||
(error: HttpQueryError) => {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
return next(error);
|
||||
}
|
||||
|
||||
if ( error.headers ) {
|
||||
Object.keys(error.headers).forEach((header) => {
|
||||
res.setHeader(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
res.setHeader(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
|
||||
res.statusCode = error.statusCode;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
});
|
||||
res.statusCode = error.statusCode;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -69,13 +84,18 @@ export interface ExpressGraphiQLOptionsFunction {
|
|||
* - (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) => {
|
||||
const query = req.url && url.parse(req.url, true).query;
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
}, error => next(error));
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(
|
||||
graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
},
|
||||
error => next(error),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,4 @@ export {
|
|||
graphiqlExpress,
|
||||
} from './expressApollo';
|
||||
|
||||
export {
|
||||
graphqlConnect,
|
||||
graphiqlConnect,
|
||||
} from './connectApollo';
|
||||
export { graphqlConnect, graphiqlConnect } from './connectApollo';
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ npm install apollo-server-hapi
|
|||
|
||||
## Usage
|
||||
|
||||
With the Hapi plugins `graphqlHapi` and `graphiqlHapi` you can pass a route object that includes options to be applied to the route. The example below enables CORS on the `/graphql` route.
|
||||
With the Hapi plugins `graphqlHapi` and `graphiqlHapi` you can pass a route object that includes options to be applied to the route. The example below enables CORS on the `/graphql` route.
|
||||
|
||||
The code below requires Hapi 17 or higher.
|
||||
|
||||
|
@ -25,31 +25,31 @@ const HOST = 'localhost';
|
|||
const PORT = 3000;
|
||||
|
||||
async function StartServer() {
|
||||
const server = new Hapi.server({
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
});
|
||||
const server = new Hapi.server({
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
});
|
||||
|
||||
await server.register({
|
||||
plugin: graphqlHapi,
|
||||
options: {
|
||||
path: '/graphql',
|
||||
graphqlOptions: {
|
||||
schema: myGraphQLSchema,
|
||||
},
|
||||
route: {
|
||||
cors: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
await server.register({
|
||||
plugin: graphqlHapi,
|
||||
options: {
|
||||
path: '/graphql',
|
||||
graphqlOptions: {
|
||||
schema: myGraphQLSchema,
|
||||
},
|
||||
route: {
|
||||
cors: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await server.start();
|
||||
} catch (err) {
|
||||
console.log(`Error while starting server: ${err.message}`);
|
||||
}
|
||||
try {
|
||||
await server.start();
|
||||
} catch (err) {
|
||||
console.log(`Error while starting server: ${err.message}`);
|
||||
}
|
||||
|
||||
console.log(`Server running at: ${server.info.uri}`);
|
||||
console.log(`Server running at: ${server.info.uri}`);
|
||||
}
|
||||
|
||||
StartServer();
|
||||
|
@ -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
|
||||
* **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!
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Hapi",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Hapi", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -2,28 +2,33 @@ import * as hapi from 'hapi';
|
|||
import { graphqlHapi, graphiqlHapi } from './hapiApollo';
|
||||
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) {
|
||||
const server = new hapi.Server({
|
||||
host: 'localhost',
|
||||
port: 8000,
|
||||
host: 'localhost',
|
||||
port: 8000,
|
||||
});
|
||||
|
||||
await server.register({
|
||||
plugin: graphqlHapi,
|
||||
options: {
|
||||
graphqlOptions: (options && options.graphqlOptions) || { schema: Schema },
|
||||
path: '/graphql',
|
||||
},
|
||||
plugin: graphqlHapi,
|
||||
options: {
|
||||
graphqlOptions: (options && options.graphqlOptions) || { schema: Schema },
|
||||
path: '/graphql',
|
||||
},
|
||||
});
|
||||
|
||||
await server.register({
|
||||
plugin: graphiqlHapi,
|
||||
options: {
|
||||
path: '/graphiql',
|
||||
graphiqlOptions: (options && options.graphiqlOptions) || { endpointURL: '/graphql' },
|
||||
plugin: graphiqlHapi,
|
||||
options: {
|
||||
path: '/graphiql',
|
||||
graphiqlOptions: (options && options.graphiqlOptions) || {
|
||||
endpointURL: '/graphql',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
|
@ -35,7 +40,7 @@ async function destroyApp(app) {
|
|||
if (!app || !app.close) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => app.close(resolve));
|
||||
await new Promise(resolve => app.close(resolve));
|
||||
}
|
||||
|
||||
describe('integration:Hapi', () => {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import * as Boom from 'boom';
|
||||
import { Server, Response, Request, ReplyNoContinue } from 'hapi';
|
||||
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 {
|
||||
(server: Server, options: any): void;
|
||||
(server: Server, options: any): void;
|
||||
}
|
||||
|
||||
export interface IPlugin {
|
||||
|
@ -48,20 +52,20 @@ const graphqlHapi: IPlugin = {
|
|||
response.type('application/json');
|
||||
return response;
|
||||
} catch (error) {
|
||||
if ( 'HttpQueryError' !== error.name ) {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
throw Boom.boomify(error);
|
||||
}
|
||||
|
||||
if ( true === error.isGraphQLError ) {
|
||||
if (true === error.isGraphQLError) {
|
||||
const response = h.response(error.message);
|
||||
response.code(error.statusCode);
|
||||
response.type('application/json');
|
||||
return response;
|
||||
}
|
||||
|
||||
const err = new Boom(error.message, {statusCode: error.statusCode});
|
||||
if ( error.headers ) {
|
||||
Object.keys(error.headers).forEach((header) => {
|
||||
const err = new Boom(error.message, { statusCode: error.statusCode });
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
err.output.headers[header] = error.headers[header];
|
||||
});
|
||||
}
|
||||
|
@ -96,7 +100,11 @@ const graphiqlHapi: IPlugin = {
|
|||
path: options.path || '/graphiql',
|
||||
config: options.route || {},
|
||||
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);
|
||||
response.type('text/html');
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
export { IRegister,
|
||||
HapiOptionsFunction,
|
||||
HapiPluginOptions,
|
||||
HapiGraphiQLOptionsFunction,
|
||||
HapiGraphiQLPluginOptions,
|
||||
graphqlHapi, graphiqlHapi } from './hapiApollo';
|
||||
export {
|
||||
IRegister,
|
||||
HapiOptionsFunction,
|
||||
HapiPluginOptions,
|
||||
HapiGraphiQLOptionsFunction,
|
||||
HapiGraphiQLPluginOptions,
|
||||
graphqlHapi,
|
||||
graphiqlHapi,
|
||||
} from './hapiApollo';
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -42,21 +42,28 @@ You can also use `apollo-server-koa` for hosting the [GraphiQL](https://github.c
|
|||
import { graphiqlKoa } from 'apollo-server-koa';
|
||||
|
||||
// Setup the /graphiql route to show the GraphiQL UI
|
||||
router.get('/graphiql', graphiqlKoa({
|
||||
endpointURL: '/graphql' // a POST endpoint that GraphiQL will make the actual requests to
|
||||
}));
|
||||
router.get(
|
||||
'/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.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
import { graphiqlKoa } from 'apollo-server-koa';
|
||||
|
||||
router.get('/graphiql', graphiqlKoa({
|
||||
router.get(
|
||||
'/graphiql',
|
||||
graphiqlKoa({
|
||||
endpointURL: '/graphql',
|
||||
passHeader: `'Authorization': 'Bearer lorem ipsum'`
|
||||
}));
|
||||
passHeader: `'Authorization': 'Bearer lorem ipsum'`,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
## 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
|
||||
* **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!
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Koa",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Koa", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -6,7 +6,10 @@ import { GraphQLOptions } from 'apollo-server-core';
|
|||
import { expect } from 'chai';
|
||||
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 = {}) {
|
||||
const app = new koa();
|
||||
|
@ -17,11 +20,11 @@ function createApp(options: CreateAppOptions = {}) {
|
|||
if (!options.excludeParser) {
|
||||
app.use(koaBody());
|
||||
}
|
||||
if (options.graphiqlOptions ) {
|
||||
router.get('/graphiql', graphiqlKoa( options.graphiqlOptions ));
|
||||
if (options.graphiqlOptions) {
|
||||
router.get('/graphiql', graphiqlKoa(options.graphiqlOptions));
|
||||
}
|
||||
router.get('/graphql', graphqlKoa( options.graphqlOptions ));
|
||||
router.post('/graphql', graphqlKoa( options.graphqlOptions ));
|
||||
router.get('/graphql', graphqlKoa(options.graphqlOptions));
|
||||
router.post('/graphql', graphqlKoa(options.graphqlOptions));
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
return http.createServer(app.callback());
|
||||
|
@ -32,13 +35,16 @@ function destroyApp(app) {
|
|||
}
|
||||
|
||||
describe('koaApollo', () => {
|
||||
it('throws error if called without schema', function(){
|
||||
expect(() => graphqlKoa(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.');
|
||||
it('throws error if called without schema', function() {
|
||||
expect(() => graphqlKoa(undefined as GraphQLOptions)).to.throw(
|
||||
'Apollo Server requires options.',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if called with more than one argument', function(){
|
||||
expect(() => (<any>graphqlKoa)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2');
|
||||
it('throws an error if called with more than one argument', function() {
|
||||
expect(() => (<any>graphqlKoa)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
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';
|
||||
|
||||
export interface KoaGraphQLOptionsFunction {
|
||||
|
@ -10,37 +14,45 @@ export interface KoaHandler {
|
|||
(req: any, next): void;
|
||||
}
|
||||
|
||||
export function graphqlKoa(options: GraphQLOptions | KoaGraphQLOptionsFunction): KoaHandler {
|
||||
export function graphqlKoa(
|
||||
options: GraphQLOptions | KoaGraphQLOptionsFunction,
|
||||
): KoaHandler {
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`);
|
||||
throw new Error(
|
||||
`Apollo Server expects exactly one argument, got ${arguments.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (ctx: koa.Context): Promise<void> => {
|
||||
return runHttpQuery([ctx], {
|
||||
method: ctx.request.method,
|
||||
options: options,
|
||||
query: ctx.request.method === 'POST' ? ctx.request.body : ctx.request.query,
|
||||
}).then((gqlResponse) => {
|
||||
ctx.set('Content-Type', 'application/json');
|
||||
ctx.body = gqlResponse;
|
||||
}, (error: HttpQueryError) => {
|
||||
if ( 'HttpQueryError' !== error.name ) {
|
||||
throw error;
|
||||
}
|
||||
query:
|
||||
ctx.request.method === 'POST' ? ctx.request.body : ctx.request.query,
|
||||
}).then(
|
||||
gqlResponse => {
|
||||
ctx.set('Content-Type', 'application/json');
|
||||
ctx.body = gqlResponse;
|
||||
},
|
||||
(error: HttpQueryError) => {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if ( error.headers ) {
|
||||
Object.keys(error.headers).forEach((header) => {
|
||||
ctx.set(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
ctx.set(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
|
||||
ctx.status = error.statusCode;
|
||||
ctx.body = error.message;
|
||||
});
|
||||
ctx.status = error.statusCode;
|
||||
ctx.body = error.message;
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,15 +60,20 @@ export interface KoaGraphiQLOptionsFunction {
|
|||
(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) => {
|
||||
const query = ctx.request.query;
|
||||
return GraphiQL.resolveGraphiQLString(query, options, ctx).then(graphiqlString => {
|
||||
ctx.set('Content-Type', 'text/html');
|
||||
ctx.body = graphiqlString;
|
||||
}, error => {
|
||||
ctx.status = 500;
|
||||
ctx.body = error.message;
|
||||
});
|
||||
return GraphiQL.resolveGraphiQLString(query, options, ctx).then(
|
||||
graphiqlString => {
|
||||
ctx.set('Content-Type', 'text/html');
|
||||
ctx.body = graphiqlString;
|
||||
},
|
||||
error => {
|
||||
ctx.status = 500;
|
||||
ctx.body = error.message;
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,8 +5,5 @@
|
|||
"outDir": "./dist",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ var server = require('apollo-server-lambda'),
|
|||
|
||||
exports.graphqlHandler = server.graphqlLambda({ schema: myGraphQLSchema });
|
||||
exports.graphiqlHandler = server.graphiqlLambda({
|
||||
endpointURL: '/Prod/graphql'
|
||||
endpointURL: '/Prod/graphql',
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -118,8 +118,8 @@ exports.graphqlHandler = server.graphqlLambda((event, context) => {
|
|||
headers,
|
||||
functionName,
|
||||
event,
|
||||
context
|
||||
}
|
||||
context,
|
||||
},
|
||||
};
|
||||
});
|
||||
```
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Lambda",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Lambda", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
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 { GraphQLOptions } from 'apollo-server-core';
|
||||
import 'mocha';
|
||||
import * as url from 'url';
|
||||
|
||||
function createLambda(options: CreateAppOptions = {}) {
|
||||
let route,
|
||||
handler,
|
||||
callback,
|
||||
event,
|
||||
context;
|
||||
let route, handler, callback, event, context;
|
||||
|
||||
options.graphqlOptions = options.graphqlOptions || { schema: Schema };
|
||||
if (options.graphiqlOptions ) {
|
||||
if (options.graphiqlOptions) {
|
||||
route = '/graphiql';
|
||||
handler = graphiqlLambda( options.graphiqlOptions );
|
||||
handler = graphiqlLambda(options.graphiqlOptions);
|
||||
} else {
|
||||
route = '/graphql';
|
||||
handler = graphqlLambda( options.graphqlOptions );
|
||||
handler = graphqlLambda(options.graphqlOptions);
|
||||
}
|
||||
|
||||
return function(req, res) {
|
||||
|
@ -29,7 +28,7 @@ function createLambda(options: CreateAppOptions = {}) {
|
|||
}
|
||||
|
||||
let body = '';
|
||||
req.on('data', function (chunk) {
|
||||
req.on('data', function(chunk) {
|
||||
body += chunk;
|
||||
});
|
||||
req.on('end', function() {
|
||||
|
@ -58,13 +57,16 @@ function createLambda(options: CreateAppOptions = {}) {
|
|||
}
|
||||
|
||||
describe('lambdaApollo', () => {
|
||||
it('throws error if called without schema', function(){
|
||||
expect(() => graphqlLambda(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.');
|
||||
it('throws error if called without schema', function() {
|
||||
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(
|
||||
'Apollo Server expects exactly one argument, got 2');
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ import { GraphQLOptions, runHttpQuery } from 'apollo-server-core';
|
|||
import * as GraphiQL from 'apollo-server-module-graphiql';
|
||||
|
||||
export interface LambdaGraphQLOptionsFunction {
|
||||
(event: any, context: lambda.Context): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
(event: any, context: lambda.Context):
|
||||
| GraphQLOptions
|
||||
| Promise<GraphQLOptions>;
|
||||
}
|
||||
|
||||
// Design principles:
|
||||
|
@ -16,23 +18,32 @@ export interface LambdaHandler {
|
|||
}
|
||||
|
||||
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) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
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) => {
|
||||
let query = (event.httpMethod === 'POST') ? event.body : event.queryStringParameters,
|
||||
return async (
|
||||
event,
|
||||
lambdaContext: lambda.Context,
|
||||
callback: lambda.Callback,
|
||||
) => {
|
||||
let query =
|
||||
event.httpMethod === 'POST' ? event.body : event.queryStringParameters,
|
||||
statusCode: number = null,
|
||||
gqlResponse = null,
|
||||
headers: {[headerName: string]: string} = {};
|
||||
headers: { [headerName: string]: string } = {};
|
||||
|
||||
if (query && typeof query === 'string') {
|
||||
query = JSON.parse(query);
|
||||
|
@ -47,7 +58,7 @@ export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFun
|
|||
headers['Content-Type'] = 'application/json';
|
||||
statusCode = 200;
|
||||
} catch (error) {
|
||||
if ( 'HttpQueryError' !== error.name ) {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -55,20 +66,19 @@ export function graphqlLambda( options: GraphQLOptions | LambdaGraphQLOptionsFun
|
|||
statusCode = error.statusCode;
|
||||
gqlResponse = error.message;
|
||||
} finally {
|
||||
callback(
|
||||
null,
|
||||
{
|
||||
'statusCode': statusCode,
|
||||
'headers': headers,
|
||||
'body': gqlResponse,
|
||||
},
|
||||
);
|
||||
callback(null, {
|
||||
statusCode: statusCode,
|
||||
headers: headers,
|
||||
body: gqlResponse,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -82,25 +92,27 @@ export interface LambdaGraphiQLOptionsFunction {
|
|||
* - (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) => {
|
||||
const query = event.queryStringParameters;
|
||||
GraphiQL.resolveGraphiQLString(query, options, event, lambdaContext).then(graphiqlString => {
|
||||
callback(
|
||||
null,
|
||||
{
|
||||
'statusCode': 200,
|
||||
'headers': {
|
||||
GraphiQL.resolveGraphiQLString(query, options, event, lambdaContext).then(
|
||||
graphiqlString => {
|
||||
callback(null, {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
'body': graphiqlString,
|
||||
},
|
||||
);
|
||||
}, error => {
|
||||
callback(null, {
|
||||
statusCode: 500,
|
||||
body: error.message,
|
||||
});
|
||||
});
|
||||
body: graphiqlString,
|
||||
});
|
||||
},
|
||||
error => {
|
||||
callback(null, {
|
||||
statusCode: 500,
|
||||
body: error.message,
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,15 +3,8 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"types": [
|
||||
"@types/node"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"types": ["@types/node"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ const server = micro(
|
|||
get('/graphql', graphqlHandler),
|
||||
post('/graphql', graphqlHandler),
|
||||
get('/graphiql', graphiqlHandler),
|
||||
(req, res) => send(res, 404, 'not found')
|
||||
)
|
||||
(req, res) => send(res, 404, 'not found'),
|
||||
),
|
||||
);
|
||||
|
||||
server.listen(3000);
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Micro",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Micro", "Server", "Javascript"],
|
||||
"author": "Nick Nance <nance.nick@gmail.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -2,38 +2,51 @@ import { microGraphql, microGraphiql } from './microApollo';
|
|||
import 'mocha';
|
||||
|
||||
import micro, { send } from 'micro';
|
||||
import { router, get, post, put, patch, del, head, options as opts } from 'microrouter';
|
||||
import testSuite, { schema, CreateAppOptions } from 'apollo-server-integration-testsuite';
|
||||
|
||||
import {
|
||||
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) {
|
||||
const graphqlOptions = (options && options.graphqlOptions) || { schema };
|
||||
const graphiqlOptions = (options && options.graphiqlOptions) || { endpointURL: '/graphql' };
|
||||
const graphqlOptions = (options && options.graphqlOptions) || { schema };
|
||||
const graphiqlOptions = (options && options.graphiqlOptions) || {
|
||||
endpointURL: '/graphql',
|
||||
};
|
||||
|
||||
const graphqlHandler = microGraphql(graphqlOptions);
|
||||
const graphiqlHandler = microGraphiql(graphiqlOptions);
|
||||
const graphqlHandler = microGraphql(graphqlOptions);
|
||||
const graphiqlHandler = microGraphiql(graphiqlOptions);
|
||||
|
||||
return micro(
|
||||
router(
|
||||
get('/graphql', graphqlHandler),
|
||||
post('/graphql', graphqlHandler),
|
||||
put('/graphql', graphqlHandler),
|
||||
patch('/graphql', graphqlHandler),
|
||||
del('/graphql', graphqlHandler),
|
||||
head('/graphql', graphqlHandler),
|
||||
opts('/graphql', graphqlHandler),
|
||||
return micro(
|
||||
router(
|
||||
get('/graphql', graphqlHandler),
|
||||
post('/graphql', graphqlHandler),
|
||||
put('/graphql', graphqlHandler),
|
||||
patch('/graphql', graphqlHandler),
|
||||
del('/graphql', graphqlHandler),
|
||||
head('/graphql', graphqlHandler),
|
||||
opts('/graphql', graphqlHandler),
|
||||
|
||||
get('/graphiql', graphiqlHandler),
|
||||
post('/graphiql', graphiqlHandler),
|
||||
put('/graphiql', graphiqlHandler),
|
||||
patch('/graphiql', graphiqlHandler),
|
||||
del('/graphiql', graphiqlHandler),
|
||||
head('/graphiql', graphiqlHandler),
|
||||
opts('/graphiql', graphiqlHandler),
|
||||
get('/graphiql', graphiqlHandler),
|
||||
post('/graphiql', graphiqlHandler),
|
||||
put('/graphiql', graphiqlHandler),
|
||||
patch('/graphiql', graphiqlHandler),
|
||||
del('/graphiql', graphiqlHandler),
|
||||
head('/graphiql', graphiqlHandler),
|
||||
opts('/graphiql', graphiqlHandler),
|
||||
|
||||
(req, res) => send(res, 404, 'not found'),
|
||||
),
|
||||
);
|
||||
(req, res) => send(res, 404, 'not found'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
describe('integration:Micro', () => {
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
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 { createError, json, RequestHandler } from 'micro';
|
||||
import * as url from 'url';
|
||||
import {IncomingMessage, ServerResponse} from 'http';
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
|
||||
export interface MicroGraphQLOptionsFunction {
|
||||
(req?: IncomingMessage): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
}
|
||||
|
||||
export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFunction): RequestHandler {
|
||||
export function microGraphql(
|
||||
options: GraphQLOptions | MicroGraphQLOptionsFunction,
|
||||
): RequestHandler {
|
||||
if (!options) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`);
|
||||
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) {
|
||||
let query;
|
||||
if (req.method === 'POST') {
|
||||
try {
|
||||
|
@ -41,7 +49,7 @@ export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFuncti
|
|||
} catch (error) {
|
||||
if ('HttpQueryError' === error.name) {
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach((header) => {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
res.setHeader(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
|
@ -57,20 +65,27 @@ export function microGraphql(options: GraphQLOptions | MicroGraphQLOptionsFuncti
|
|||
}
|
||||
|
||||
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) => {
|
||||
const query = req.url && url.parse(req.url, true).query || {};
|
||||
return GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
}, error => {
|
||||
res.statusCode = 500;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
});
|
||||
const query = (req.url && url.parse(req.url, true).query) || {};
|
||||
return GraphiQL.resolveGraphiQLString(query, options, req).then(
|
||||
graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
},
|
||||
error => {
|
||||
res.statusCode = 500;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,14 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"GraphiQL",
|
||||
"Apollo",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "GraphiQL", "Apollo", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -21,15 +21,15 @@
|
|||
*/
|
||||
|
||||
export type GraphiQLData = {
|
||||
endpointURL: string,
|
||||
subscriptionsEndpoint?: string,
|
||||
query?: string,
|
||||
variables?: Object,
|
||||
operationName?: string,
|
||||
result?: Object,
|
||||
passHeader?: string,
|
||||
editorTheme?: string,
|
||||
websocketConnectionParams?: Object,
|
||||
endpointURL: string;
|
||||
subscriptionsEndpoint?: string;
|
||||
query?: string;
|
||||
variables?: Object;
|
||||
operationName?: string;
|
||||
result?: Object;
|
||||
passHeader?: string;
|
||||
editorTheme?: string;
|
||||
websocketConnectionParams?: Object;
|
||||
};
|
||||
|
||||
// Current latest version of GraphiQL.
|
||||
|
@ -44,15 +44,18 @@ function safeSerialize(data) {
|
|||
|
||||
export function renderGraphiQL(data: GraphiQLData): string {
|
||||
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 usingHttp = !endpointWs;
|
||||
const usingWs = endpointWs || !!subscriptionsEndpoint;
|
||||
const endpointURLWs = usingWs && (endpointWs ? endpointURL : subscriptionsEndpoint);
|
||||
const endpointURLWs =
|
||||
usingWs && (endpointWs ? endpointURL : subscriptionsEndpoint);
|
||||
|
||||
const queryString = data.query;
|
||||
const variablesString =
|
||||
data.variables ? JSON.stringify(data.variables, null, 2) : null;
|
||||
const variablesString = data.variables
|
||||
? JSON.stringify(data.variables, null, 2)
|
||||
: null;
|
||||
const resultString = null;
|
||||
const operationName = data.operationName;
|
||||
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-dom@15.6.1/dist/react-dom.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" />`
|
||||
: ''}
|
||||
${usingHttp ?
|
||||
`<script src="//cdn.jsdelivr.net/fetch/2.0.1/fetch.min.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>'
|
||||
: ''}
|
||||
${
|
||||
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
|
||||
? `<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>
|
||||
<body>
|
||||
|
@ -125,16 +136,26 @@ export function renderGraphiQL(data: GraphiQLData): string {
|
|||
}
|
||||
}
|
||||
|
||||
${usingWs ? `
|
||||
${
|
||||
usingWs
|
||||
? `
|
||||
var subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient('${endpointURLWs}', {
|
||||
reconnect: true${websocketConnectionParams ? `,
|
||||
connectionParams: ${JSON.stringify(websocketConnectionParams)}` : '' }
|
||||
reconnect: true${
|
||||
websocketConnectionParams
|
||||
? `,
|
||||
connectionParams: ${JSON.stringify(websocketConnectionParams)}`
|
||||
: ''
|
||||
}
|
||||
});
|
||||
|
||||
var graphQLWSFetcher = subscriptionsClient.request.bind(subscriptionsClient);
|
||||
` : ''}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
|
||||
${usingHttp ? `
|
||||
${
|
||||
usingHttp
|
||||
? `
|
||||
// We don't use safe-serialize for location, because it's not client input.
|
||||
var fetchURL = locationQuery(otherParams, '${endpointURL}');
|
||||
|
||||
|
@ -159,14 +180,20 @@ export function renderGraphiQL(data: GraphiQLData): string {
|
|||
}
|
||||
});
|
||||
}
|
||||
` : ''}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
|
||||
${usingWs && usingHttp ? `
|
||||
${
|
||||
usingWs && usingHttp
|
||||
? `
|
||||
var fetcher =
|
||||
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
|
||||
// that it can be easily shared.
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import { GraphiQLData, renderGraphiQL } from './renderGraphiQL';
|
||||
|
||||
export type GraphiQLParams = {
|
||||
query?: string,
|
||||
variables?: string,
|
||||
operationName?: string,
|
||||
query?: string;
|
||||
variables?: string;
|
||||
operationName?: string;
|
||||
};
|
||||
|
||||
function isOptionsFunction(arg: GraphiQLData | Function): arg is 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)) {
|
||||
try {
|
||||
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 {
|
||||
endpointURL: options.endpointURL,
|
||||
subscriptionsEndpoint: options.subscriptionsEndpoint,
|
||||
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,
|
||||
passHeader: options.passHeader,
|
||||
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 graphiqlOptions = await resolveGraphiQLOptions(options, ...args);
|
||||
const graphiqlData = createGraphiQLData(graphiqlParams, graphiqlOptions);
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,14 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Operation Store",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Operation Store", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -1,132 +1,132 @@
|
|||
import 'mocha';
|
||||
|
||||
import {
|
||||
expect,
|
||||
} from 'chai';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import {
|
||||
GraphQLSchema,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
print,
|
||||
parse,
|
||||
GraphQLSchema,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
print,
|
||||
parse,
|
||||
} from 'graphql';
|
||||
|
||||
import { OperationStore } from './operationStore';
|
||||
|
||||
const queryType = new GraphQLObjectType({
|
||||
name: 'QueryType',
|
||||
fields: {
|
||||
testString: {
|
||||
type: GraphQLString,
|
||||
/*resolve() {
|
||||
name: 'QueryType',
|
||||
fields: {
|
||||
testString: {
|
||||
type: GraphQLString,
|
||||
/*resolve() {
|
||||
return 'it works';
|
||||
},*/
|
||||
},
|
||||
testRootValue: {
|
||||
type: GraphQLString,
|
||||
/*resolve(root) {
|
||||
},
|
||||
testRootValue: {
|
||||
type: GraphQLString,
|
||||
/*resolve(root) {
|
||||
return root + ' works';
|
||||
},*/
|
||||
},
|
||||
testContextValue: {
|
||||
type: GraphQLString,
|
||||
/*resolve(root, args, context) {
|
||||
},
|
||||
testContextValue: {
|
||||
type: GraphQLString,
|
||||
/*resolve(root, args, context) {
|
||||
return context + ' works';
|
||||
},*/
|
||||
},
|
||||
testArgumentValue: {
|
||||
type: GraphQLInt,
|
||||
/*resolve(root, args, context) {
|
||||
},
|
||||
testArgumentValue: {
|
||||
type: GraphQLInt,
|
||||
/*resolve(root, args, context) {
|
||||
return args['base'] + 5;
|
||||
},*/
|
||||
args: {
|
||||
base: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
base: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const schema = new GraphQLSchema({
|
||||
query: queryType,
|
||||
query: queryType,
|
||||
});
|
||||
|
||||
describe('operationStore', () => {
|
||||
it('can store a query and return its ast', () => {
|
||||
const query = `query testquery{ testString }`;
|
||||
const expected = `query testquery {\n testString\n}\n`;
|
||||
const query = `query testquery{ testString }`;
|
||||
const expected = `query testquery {\n testString\n}\n`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
store.put(query);
|
||||
const store = new OperationStore(schema);
|
||||
store.put(query);
|
||||
|
||||
return expect(print(store.get('testquery'))).to.deep.equal(expected);
|
||||
return expect(print(store.get('testquery'))).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('can store a Document and return its ast', () => {
|
||||
const query = `query testquery{ testString }`;
|
||||
const expected = `query testquery {\n testString\n}\n`;
|
||||
const query = `query testquery{ testString }`;
|
||||
const expected = `query testquery {\n testString\n}\n`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
store.put(parse(query));
|
||||
const store = new OperationStore(schema);
|
||||
store.put(parse(query));
|
||||
|
||||
return expect(print(store.get('testquery'))).to.deep.equal(expected);
|
||||
return expect(print(store.get('testquery'))).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('can store queries and return them with getMap', () => {
|
||||
const query = `query testquery{ testString }`;
|
||||
const query2 = `query testquery2{ testRootValue }`;
|
||||
const query = `query testquery{ testString }`;
|
||||
const query2 = `query testquery2{ testRootValue }`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
store.put(query);
|
||||
store.put(query2);
|
||||
return expect(store.getMap().size).to.equal(2);
|
||||
const store = new OperationStore(schema);
|
||||
store.put(query);
|
||||
store.put(query2);
|
||||
return expect(store.getMap().size).to.equal(2);
|
||||
});
|
||||
|
||||
it('throws a parse error if the query is invalid', () => {
|
||||
const query = `query testquery{ testString`;
|
||||
const query = `query testquery{ testString`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
return expect(() => store.put(query)).to.throw(/Syntax Error GraphQL/);
|
||||
const store = new OperationStore(schema);
|
||||
return expect(() => store.put(query)).to.throw(/Syntax Error GraphQL/);
|
||||
});
|
||||
|
||||
it('throws a validation error if the query is invalid', () => {
|
||||
const query = `query testquery { testStrin }`;
|
||||
const query = `query testquery { testStrin }`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
return expect(() => store.put(query)).to.throw(/Cannot query field/);
|
||||
const store = new OperationStore(schema);
|
||||
return expect(() => store.put(query)).to.throw(/Cannot query field/);
|
||||
});
|
||||
|
||||
it('throws an error if there is more than one query or mutation', () => {
|
||||
const query = `
|
||||
const query = `
|
||||
query Q1{ testString }
|
||||
query Q2{ t2: testString }
|
||||
`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
return expect(() => store.put(query)).to.throw(/OperationDefinitionNode must contain only one definition/);
|
||||
const store = new OperationStore(schema);
|
||||
return expect(() => store.put(query)).to.throw(
|
||||
/OperationDefinitionNode must contain only one definition/,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if there is no operationDefinition found', () => {
|
||||
const query = `
|
||||
const query = `
|
||||
schema {
|
||||
query: Q
|
||||
}
|
||||
`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
const store = new OperationStore(schema);
|
||||
|
||||
return expect(() => store.put(query)).to.throw(/must contain at least/);
|
||||
return expect(() => store.put(query)).to.throw(/must contain at least/);
|
||||
});
|
||||
|
||||
it('can delete stored operations', () => {
|
||||
const query = `query testquery{ testString }`;
|
||||
const query = `query testquery{ testString }`;
|
||||
|
||||
const store = new OperationStore(schema);
|
||||
store.put(query);
|
||||
store.delete('testquery');
|
||||
const store = new OperationStore(schema);
|
||||
store.put(query);
|
||||
store.delete('testquery');
|
||||
|
||||
return expect(store.get('testquery')).to.be.undefined;
|
||||
return expect(store.get('testquery')).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,9 @@ export class OperationStore {
|
|||
}
|
||||
|
||||
public put(operation: string | DocumentNode): void {
|
||||
function isOperationDefinition(definition): definition is OperationDefinitionNode {
|
||||
function isOperationDefinition(
|
||||
definition,
|
||||
): definition is OperationDefinitionNode {
|
||||
return definition.kind === Kind.OPERATION_DEFINITION;
|
||||
}
|
||||
|
||||
|
@ -25,19 +27,27 @@ export class OperationStore {
|
|||
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) {
|
||||
throw new Error('OperationDefinitionNode must contain at least one definition');
|
||||
throw new Error(
|
||||
'OperationDefinitionNode must contain at least one definition',
|
||||
);
|
||||
}
|
||||
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);
|
||||
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')}`);
|
||||
err['originalErrors'] = validationErrors;
|
||||
throw err;
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { graphqlRestify, graphiqlRestify } from 'apollo-server-restify';
|
|||
const PORT = 3000;
|
||||
|
||||
const server = restify.createServer({
|
||||
title: 'Apollo Server'
|
||||
title: 'Apollo Server',
|
||||
});
|
||||
|
||||
const graphQLOptions = { schema: myGraphQLSchema };
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Restify",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Restify", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import 'mocha';
|
||||
import * as restify from 'restify';
|
||||
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 { GraphQLOptions } from 'apollo-server-core';
|
||||
|
||||
|
@ -16,8 +19,8 @@ function createApp(options: CreateAppOptions = {}) {
|
|||
server.use(restify.plugins.queryParser());
|
||||
}
|
||||
|
||||
if (options.graphiqlOptions ) {
|
||||
server.get('/graphiql', graphiqlRestify( options.graphiqlOptions ));
|
||||
if (options.graphiqlOptions) {
|
||||
server.get('/graphiql', graphiqlRestify(options.graphiqlOptions));
|
||||
}
|
||||
|
||||
server.get('/graphql', graphqlRestify(options.graphqlOptions));
|
||||
|
@ -28,12 +31,15 @@ function createApp(options: CreateAppOptions = {}) {
|
|||
|
||||
describe('graphqlRestify', () => {
|
||||
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', () => {
|
||||
expect(() => (<any>graphqlRestify)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2');
|
||||
expect(() => (<any>graphqlRestify)({}, 'x')).to.throw(
|
||||
'Apollo Server expects exactly one argument, got 2',
|
||||
);
|
||||
});
|
||||
|
||||
it('generates a function if the options are ok', () => {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import * as restify from 'restify';
|
||||
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';
|
||||
|
||||
export interface RestifyGraphQLOptionsFunction {
|
||||
(req?: restify.Request, res?: restify.Response): GraphQLOptions | Promise<GraphQLOptions>;
|
||||
(req?: restify.Request, res?: restify.Response):
|
||||
| GraphQLOptions
|
||||
| Promise<GraphQLOptions>;
|
||||
}
|
||||
|
||||
// Design principles:
|
||||
|
@ -16,46 +22,59 @@ export interface RestifyHandler {
|
|||
(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) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
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], {
|
||||
method: req.method,
|
||||
options: options,
|
||||
query: req.method === 'POST' ? req.body : req.query,
|
||||
}).then((gqlResponse) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.write(gqlResponse);
|
||||
res.end();
|
||||
next();
|
||||
}, (error: HttpQueryError) => {
|
||||
if ( 'HttpQueryError' !== error.name ) {
|
||||
throw error;
|
||||
}
|
||||
}).then(
|
||||
gqlResponse => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.write(gqlResponse);
|
||||
res.end();
|
||||
next();
|
||||
},
|
||||
(error: HttpQueryError) => {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if ( error.headers ) {
|
||||
Object.keys(error.headers).forEach((header) => {
|
||||
res.setHeader(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
res.setHeader(header, error.headers[header]);
|
||||
});
|
||||
}
|
||||
|
||||
res.statusCode = error.statusCode;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
next(false);
|
||||
});
|
||||
res.statusCode = error.statusCode;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
next(false);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -69,19 +88,24 @@ export interface RestifyGraphiQLOptionsFunction {
|
|||
* - (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) => {
|
||||
const query = req.url && url.parse(req.url, true).query || {};
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
next();
|
||||
}, error => {
|
||||
res.statusCode = 500;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
next(false);
|
||||
});
|
||||
const query = (req.url && url.parse(req.url, true).query) || {};
|
||||
GraphiQL.resolveGraphiQLString(query, options, req).then(
|
||||
graphiqlString => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiqlString);
|
||||
res.end();
|
||||
next();
|
||||
},
|
||||
error => {
|
||||
res.statusCode = 500;
|
||||
res.write(error.message);
|
||||
res.end();
|
||||
next(false);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,8 +5,5 @@
|
|||
"outDir": "./dist",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,14 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"name": "graphql-server-express",
|
||||
"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",
|
||||
"scripts": {
|
||||
"compile": "tsc",
|
||||
|
@ -9,7 +10,8 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Hapi",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Hapi", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Koa",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Koa", "Server", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Lambda",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Lambda", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Micro",
|
||||
"Server",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Micro", "Server", "Javascript"],
|
||||
"author": "Nick Nance <nance.nick@gmail.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,14 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"GraphiQL",
|
||||
"Apollo",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "GraphiQL", "Apollo", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,14 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Operation Store",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Operation Store", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -9,15 +9,10 @@
|
|||
},
|
||||
"repository": {
|
||||
"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": [
|
||||
"GraphQL",
|
||||
"Apollo",
|
||||
"Server",
|
||||
"Restify",
|
||||
"Javascript"
|
||||
],
|
||||
"keywords": ["GraphQL", "Apollo", "Server", "Restify", "Javascript"],
|
||||
"author": "Jonas Helfer <jonas@helfer.email>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
|
|
@ -4,8 +4,5 @@
|
|||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
const NODE_VERSION = process.version.split('.');
|
||||
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';
|
||||
|
||||
require('../packages/apollo-server-core/dist/runQuery.test.js');
|
||||
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/connectApollo.test');
|
||||
(NODE_MAJOR_VERSION >= 9 || (NODE_MAJOR_VERSION >= 8 && NODE_MAJOR_REVISION >= 9))
|
||||
&& require('../packages/apollo-server-hapi/dist/hapiApollo.test'); // Hapi 17 is 8.9+
|
||||
(NODE_MAJOR_VERSION >= 6) && require('../packages/apollo-server-micro/dist/microApollo.test');
|
||||
(NODE_MAJOR_VERSION >= 7) && require('../packages/apollo-server-koa/dist/koaApollo.test');
|
||||
(NODE_MAJOR_VERSION >= 9 ||
|
||||
(NODE_MAJOR_VERSION >= 8 && NODE_MAJOR_REVISION >= 9)) &&
|
||||
require('../packages/apollo-server-hapi/dist/hapiApollo.test'); // Hapi 17 is 8.9+
|
||||
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-azure-functions/dist/azureFunctionsApollo.test');
|
||||
require('../packages/apollo-server-express/dist/apolloServerHttp.test');
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
"pretty": true,
|
||||
"removeComments": true,
|
||||
"lib": ["es6", "esnext.asynciterable"],
|
||||
"types": [
|
||||
"@types/node"
|
||||
]
|
||||
"types": ["@types/node"]
|
||||
}
|
||||
}
|
||||
|
|
133
tslint.json
133
tslint.json
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue