mirror of
https://github.com/vale981/apollo-server
synced 2025-03-06 10:11:40 -05:00
Merge branch 'master' of https://github.com/apollostack/apollo-server
This commit is contained in:
commit
b0eb066dd1
16 changed files with 265 additions and 114 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
### VNEXT
|
||||
|
||||
* Fix passHeader option in GraphiQL (Both Hapi and Koa)
|
||||
* Pass `ctx` instead of `ctx.request` to options function in Koa integration ([@HriBB](https://github.com/HriBB)) in [PR #154](https://github.com/apollostack/apollo-server/pull/154)
|
||||
* Manage TypeScript declaration files using npm. ([@od1k](https:/github.com/od1k) in [#162](https://github.com/apollostack/apollo-server/pull/162))
|
||||
* Fix connect example in readme. ([@conrad-vanl](https://github.com/conrad-vanl) in [#165](https://github.com/apollostack/apollo-server/pull/165))
|
||||
|
||||
### 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)
|
||||
|
@ -11,6 +24,11 @@
|
|||
[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)
|
||||
* Improve logging function. Issue #79. ([@nnance](https://github.com/nnance)) in
|
||||
[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)
|
||||
* 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))
|
||||
|
|
51
README.md
51
README.md
|
@ -21,18 +21,11 @@ Anyone is welcome to contribute to Apollo Server, just read [CONTRIBUTING.md](./
|
|||
|
||||
## Getting started
|
||||
|
||||
Apollo Server is super-easy to set up. Just npm-install apollo-server, write a GraphQL schema, and then use one of the following snippets to get started. For more info, read the [Apollo Server docs](http://docs.apollostack.com/apollo-server).
|
||||
Apollo Server is super-easy to set up. Just npm-install apollo-server, write a GraphQL schema, and then use one of the following snippets to get started. For more info, read the [Apollo Server docs](http://dev.apollodata.com/tools/apollo-server/index.html).
|
||||
|
||||
### TypeScript
|
||||
### Installation
|
||||
|
||||
If you want to build your GraphQL server using TypeScript, Apollo Server is the project for you. **NOTE**: All typings mentioned below must be included in your project in order for it to compile.
|
||||
|
||||
```sh
|
||||
npm install apollo-server
|
||||
typings i -SG dt~express dt~express-serve-static-core dt~serve-static dt~mime dt~hapi dt~boom dt~cookies dt~koa
|
||||
```
|
||||
|
||||
For using the project in JavaScript, just run `npm install --save apollo-server` and you're good to go!
|
||||
Just run `npm install --save apollo-server` and you're good to go!
|
||||
|
||||
### Express
|
||||
|
||||
|
@ -53,24 +46,27 @@ app.listen(PORT);
|
|||
### Connect
|
||||
```js
|
||||
import connect from 'connect';
|
||||
import bodyParser from 'body-parser';
|
||||
import { apolloConnect } from 'apollo-server';
|
||||
import http from 'http';
|
||||
|
||||
const PORT = 3000;
|
||||
|
||||
var app = connect();
|
||||
|
||||
app.use('/graphql', bodyParser.json(), apolloConnect({ schema: myGraphQLSchema }));
|
||||
app.use('/graphql', bodyParser.json());
|
||||
app.use('/graphql', apolloConnect({ schema: myGraphQLSchema }));
|
||||
|
||||
app.listen(PORT);
|
||||
http.createServer(app).listen(PORT);
|
||||
```
|
||||
|
||||
### Hapi
|
||||
|
||||
Now with the Hapi plugins `ApolloHapi` 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 `apolloHapi` 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.
|
||||
|
||||
```js
|
||||
import hapi from 'hapi';
|
||||
import { ApolloHapi } from 'apollo-server';
|
||||
import { apolloHapi } from 'apollo-server';
|
||||
|
||||
const server = new hapi.Server();
|
||||
|
||||
|
@ -83,7 +79,7 @@ server.connection({
|
|||
});
|
||||
|
||||
server.register({
|
||||
register: ApolloHapi,
|
||||
register: apolloHapi,
|
||||
options: {
|
||||
path: '/graphql',
|
||||
apolloOptions: {
|
||||
|
@ -166,3 +162,28 @@ Apollo Server and express-graphql are more or less the same thing (GraphQL middl
|
|||
Despite express-graphql being a reference implementation, Apollo Server is actually easier to understand and more modular than express-graphql.
|
||||
|
||||
That said, Apollo Server is heavily inspired by express-graphql (it's the reference implementation after all). Rather than seeing the two as competing alternatives, we think that they both have separate roles in the GraphQL ecosystem: express-graphql is a reference implementation, and Apollo Server is a GraphQL server to be used in production and evolve quickly with the needs of the community. Over time, express-graphql can adopt those features of Apollo Server that have proven their worth and become established more widely.
|
||||
|
||||
## Apollo Server Development
|
||||
|
||||
If you want to develop apollo server locally you must follow the following instructions:
|
||||
|
||||
* Fork this repository
|
||||
|
||||
* Install the Apollo Server project in your computer
|
||||
|
||||
```
|
||||
git clone https://github.com/[your-user]/apollo-server
|
||||
cd apollo-server
|
||||
npm install -g typescript live-server
|
||||
npm install
|
||||
npm run typings
|
||||
npm run compile
|
||||
npm link
|
||||
```
|
||||
|
||||
* Install your local Apollo Server in other App
|
||||
|
||||
```
|
||||
cd ~/myApp
|
||||
npm link apollo-server
|
||||
```
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as bodyParser from "body-parser";
|
|||
import { graphqlHTTP, renderGraphiQL } from "apollo-server";
|
||||
|
||||
const port = 3000;
|
||||
const endpointURL = "/grahpql";
|
||||
const endpointURL = "/graphql";
|
||||
const app = express();
|
||||
|
||||
const schema = new graphql.GraphQLSchema({
|
||||
|
|
35
package.json
35
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server",
|
||||
"version": "0.2.8",
|
||||
"version": "0.3.2",
|
||||
"description": "Production-ready Node.js GraphQL server for Express, Hapi, Koa",
|
||||
"main": "dist/index.js",
|
||||
"directories": {
|
||||
|
@ -37,20 +37,40 @@
|
|||
},
|
||||
"homepage": "https://github.com/apollostack/apollo-proxy#readme",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "0.0.33",
|
||||
"@types/boom": "0.0.32",
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/connect": "^3.4.30",
|
||||
"@types/cookies": "^0.5.30",
|
||||
"@types/express": "^4.0.33",
|
||||
"@types/express-serve-static-core": "^4.0.36",
|
||||
"@types/fibers": "0.0.29",
|
||||
"@types/hapi": "^13.0.35",
|
||||
"@types/http-errors": "^1.3.29",
|
||||
"@types/koa": "^2.0.33",
|
||||
"@types/koa-bodyparser": "^3.0.19",
|
||||
"@types/koa-router": "^7.0.21",
|
||||
"@types/mime": "0.0.29",
|
||||
"@types/multer": "0.0.32",
|
||||
"@types/node": "^6.0.41",
|
||||
"@types/serve-static": "^1.7.31",
|
||||
"boom": "^4.0.0",
|
||||
"http-errors": "^1.5.0",
|
||||
"source-map-support": "^0.4.2"
|
||||
"source-map-support": "^0.4.2",
|
||||
"typed-graphql": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.32",
|
||||
"@types/sinon": "^1.16.31",
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-core": "^6.11.4",
|
||||
"babel-polyfill": "^6.9.1",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
"body-parser": "^1.15.2",
|
||||
"boom": "^4.0.0",
|
||||
"chai": "^3.5.0",
|
||||
"connect": "^3.4.1",
|
||||
"express": "^4.14.0",
|
||||
"fibers": "^1.0.13",
|
||||
"fibers": "^1.0.15",
|
||||
"graphql": "^0.7.0",
|
||||
"hapi": "^15.0.3",
|
||||
"istanbul": "1.0.0-alpha.2",
|
||||
|
@ -58,13 +78,14 @@
|
|||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-router": "^7.0.1",
|
||||
"meteor-promise": "^0.7.3",
|
||||
"mocha": "^3.0.0",
|
||||
"mocha": "^3.1.1",
|
||||
"multer": "^1.1.0",
|
||||
"remap-istanbul": "^0.6.4",
|
||||
"remap-istanbul": "^0.7.0",
|
||||
"sinon": "^1.17.5",
|
||||
"supertest": "^2.0.0",
|
||||
"supertest-as-promised": "^4.0.0",
|
||||
"tslint": "^3.13.0",
|
||||
"typescript": "^1.8.10",
|
||||
"typescript": "^2.0.3",
|
||||
"typings": "^1.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
expect,
|
||||
} from 'chai';
|
||||
import { expect } from 'chai';
|
||||
import { stub } from 'sinon';
|
||||
|
||||
import {
|
||||
GraphQLSchema,
|
||||
|
@ -11,7 +10,11 @@ import {
|
|||
parse,
|
||||
} from 'graphql';
|
||||
|
||||
import { runQuery } from './runQuery';
|
||||
import {
|
||||
runQuery,
|
||||
LogAction,
|
||||
LogStep,
|
||||
} from './runQuery';
|
||||
|
||||
// Make the global Promise constructor Fiber-aware to simulate a Meteor
|
||||
// environment.
|
||||
|
@ -58,6 +61,12 @@ const QueryType = new GraphQLObjectType({
|
|||
return 'it ' + (<any>Promise).await('works');
|
||||
},
|
||||
},
|
||||
testError: {
|
||||
type: GraphQLString,
|
||||
resolve() {
|
||||
throw new Error('Secret error message');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -84,18 +93,46 @@ describe('runQuery', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('returns a syntax error if the query string contains one', () => {
|
||||
const query = `query { test`;
|
||||
const expected = /Syntax Error GraphQL/;
|
||||
return runQuery({
|
||||
schema: 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);
|
||||
});
|
||||
it('returns a syntax error if the query string contains one', () => {
|
||||
const query = `query { test `;
|
||||
const expected = /Syntax Error GraphQL/;
|
||||
return runQuery({
|
||||
schema: 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);
|
||||
});
|
||||
});
|
||||
|
||||
it('sends stack trace to error if in an error occurs and debug mode is set', () => {
|
||||
const query = `query { testError }`;
|
||||
const expected = /at resolveOrError/;
|
||||
const logStub = stub(console, 'error');
|
||||
return runQuery({
|
||||
schema: 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);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send stack trace if in an error occurs and not in debug mode', () => {
|
||||
const query = `query { testError }`;
|
||||
const logStub = stub(console, 'error');
|
||||
return runQuery({
|
||||
schema: 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', () => {
|
||||
|
@ -219,9 +256,7 @@ describe('runQuery', () => {
|
|||
testString
|
||||
}`;
|
||||
const logs = [];
|
||||
const logFn = (...args) => {
|
||||
logs.push(args);
|
||||
};
|
||||
const logFn = (obj) => logs.push(obj);
|
||||
const expected = {
|
||||
testString: 'it works',
|
||||
};
|
||||
|
@ -235,14 +270,11 @@ describe('runQuery', () => {
|
|||
.then((res) => {
|
||||
expect(res.data).to.deep.equal(expected);
|
||||
expect(logs.length).to.equals(11);
|
||||
expect(logs[0][0]).to.equals('request.start');
|
||||
expect(logs[1][0]).to.equals('request.query');
|
||||
expect(logs[1][1]).to.deep.equals(query);
|
||||
expect(logs[2][0]).to.equals('request.variables');
|
||||
expect(logs[2][1]).to.deep.equals({ test: 123 });
|
||||
expect(logs[3][0]).to.equals('request.operationName');
|
||||
expect(logs[3][1]).to.equals('Q1');
|
||||
expect(logs[10][0]).to.equals('request.end');
|
||||
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});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,25 @@ export interface GqlResponse {
|
|||
errors?: Array<string>;
|
||||
}
|
||||
|
||||
export enum LogAction {
|
||||
request, parse, validation, execute
|
||||
}
|
||||
|
||||
export enum LogStep {
|
||||
start, end, status
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
action: LogAction;
|
||||
step: LogStep;
|
||||
key?: string;
|
||||
data?: Object;
|
||||
}
|
||||
|
||||
export interface LogFunction {
|
||||
(message: LogMessage);
|
||||
}
|
||||
|
||||
export interface QueryOptions {
|
||||
schema: GraphQLSchema;
|
||||
query: string | Document;
|
||||
|
@ -23,13 +42,14 @@ export interface QueryOptions {
|
|||
context?: any;
|
||||
variables?: { [key: string]: any };
|
||||
operationName?: string;
|
||||
logFunction?: Function;
|
||||
logFunction?: LogFunction;
|
||||
validationRules?: Array<ValidationRule>;
|
||||
// WARNING: these extra validation rules are only applied to queries
|
||||
// submitted as string, not those submitted as Document!
|
||||
|
||||
formatError?: Function;
|
||||
formatResponse?: Function;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
const resolvedPromise = Promise.resolve();
|
||||
|
@ -43,8 +63,10 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
|||
let documentAST: Document;
|
||||
|
||||
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('request.start');
|
||||
logFunction({action: LogAction.request, step: LogStep.start});
|
||||
|
||||
function format(errors: Array<Error>): Array<Error> {
|
||||
// TODO: fix types! shouldn't have to cast.
|
||||
|
@ -54,19 +76,24 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
|||
return errors.map(options.formatError || formatError as any) as Array<Error>;
|
||||
}
|
||||
|
||||
logFunction('request.query', typeof options.query === 'string' ? options.query : print(options.query));
|
||||
logFunction('request.variables', options.variables);
|
||||
logFunction('request.operationName', options.operationName);
|
||||
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
|
||||
if (typeof options.query === 'string') {
|
||||
try {
|
||||
// TODO: time this with log function
|
||||
logFunction('parse.start');
|
||||
logFunction({action: LogAction.parse, step: LogStep.start});
|
||||
documentAST = parse(options.query as string);
|
||||
logFunction('parse.end');
|
||||
logFunction({action: LogAction.parse, step: LogStep.end});
|
||||
} catch (syntaxError) {
|
||||
logFunction('parse.end');
|
||||
logFunction({action: LogAction.parse, step: LogStep.end});
|
||||
return Promise.resolve({ errors: format([syntaxError]) });
|
||||
}
|
||||
|
||||
|
@ -76,9 +103,9 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
|||
if (options.validationRules) {
|
||||
rules = rules.concat(options.validationRules);
|
||||
}
|
||||
logFunction('validation.start');
|
||||
logFunction({action: LogAction.validation, step: LogStep.start});
|
||||
const validationErrors = validate(options.schema, documentAST, rules);
|
||||
logFunction('validation.end');
|
||||
logFunction({action: LogAction.validation, step: LogStep.end});
|
||||
if (validationErrors.length) {
|
||||
return Promise.resolve({ errors: format(validationErrors) });
|
||||
}
|
||||
|
@ -87,7 +114,7 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
|||
}
|
||||
|
||||
try {
|
||||
logFunction('execution.start');
|
||||
logFunction({action: LogAction.execute, step: LogStep.start});
|
||||
return execute(
|
||||
options.schema,
|
||||
documentAST,
|
||||
|
@ -96,13 +123,16 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
|||
options.variables,
|
||||
options.operationName
|
||||
).then(gqlResponse => {
|
||||
logFunction('execution.end');
|
||||
logFunction('request.end');
|
||||
logFunction({action: LogAction.execute, step: LogStep.end});
|
||||
logFunction({action: LogAction.request, step: LogStep.end});
|
||||
let response = {
|
||||
data: gqlResponse.data,
|
||||
};
|
||||
if (gqlResponse.errors) {
|
||||
response['errors'] = format(gqlResponse.errors);
|
||||
if (debug) {
|
||||
gqlResponse.errors.map(printStackTrace);
|
||||
}
|
||||
}
|
||||
if (options.formatResponse) {
|
||||
response = options.formatResponse(response, options);
|
||||
|
@ -110,8 +140,8 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
|||
return response;
|
||||
});
|
||||
} catch (executionError) {
|
||||
logFunction('execution.end');
|
||||
logFunction('request.end');
|
||||
logFunction({action: LogAction.execute, step: LogStep.end});
|
||||
logFunction({action: LogAction.request, step: LogStep.end});
|
||||
return Promise.resolve({ errors: format([executionError]) });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
export { runQuery } from './core/runQuery'
|
||||
export { runQuery, LogFunction, LogMessage, LogStep, LogAction } from './core/runQuery'
|
||||
export { renderGraphiQL} from './modules/renderGraphiQL'
|
||||
export { OperationStore } from './modules/operationStore'
|
||||
export { apolloExpress, graphiqlExpress } from './integrations/expressApollo'
|
||||
export { ApolloHapi, GraphiQLHapi } from './integrations/hapiApollo'
|
||||
export { apolloHapi, graphiqlHapi, HapiPluginOptions, HapiOptionsFunction } from './integrations/hapiApollo'
|
||||
export { apolloKoa, graphiqlKoa } from './integrations/koaApollo'
|
||||
export { apolloConnect, graphiqlConnect } from './integrations/connectApollo'
|
||||
export { default as ApolloOptions} from './integrations/apolloOptions'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as graphql from 'graphql';
|
||||
import { GraphQLSchema, ValidationRule } from 'graphql';
|
||||
import { LogFunction } from '../core/runQuery';
|
||||
|
||||
/*
|
||||
* ExpressApolloOptions
|
||||
|
@ -11,17 +12,19 @@ import * as graphql from 'graphql';
|
|||
* - (optional) formatParams: a function applied to the parameters of every invocation of runQuery
|
||||
* - (optional) validationRules: extra validation rules applied to requests
|
||||
* - (optional) formatResponse: a function applied to each graphQL execution result
|
||||
* - (optional) debug: a boolean that will print additional debug logging if execution errors occur
|
||||
*
|
||||
*/
|
||||
interface ApolloOptions {
|
||||
schema: graphql.GraphQLSchema;
|
||||
schema: GraphQLSchema;
|
||||
formatError?: Function;
|
||||
rootValue?: any;
|
||||
context?: any;
|
||||
logFunction?: Function;
|
||||
logFunction?: LogFunction;
|
||||
formatParams?: Function;
|
||||
validationRules?: Array<graphql.ValidationRule>;
|
||||
validationRules?: Array<ValidationRule>;
|
||||
formatResponse?: Function;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export default ApolloOptions;
|
||||
|
|
|
@ -98,6 +98,7 @@ export function apolloExpress(options: ApolloOptions | ExpressApolloOptionsFunct
|
|||
validationRules: optionsObject.validationRules,
|
||||
formatError: formatErrorFn,
|
||||
formatResponse: optionsObject.formatResponse,
|
||||
debug: optionsObject.debug,
|
||||
};
|
||||
|
||||
if (optionsObject.formatParams) {
|
||||
|
@ -154,6 +155,7 @@ export function graphiqlExpress(options: GraphiQL.GraphiQLData) {
|
|||
query: query || options.query,
|
||||
variables: JSON.parse(variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(graphiQLString);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as hapi from 'hapi';
|
||||
import { ApolloHapi, GraphiQLHapi, HapiPluginOptions } from './hapiApollo';
|
||||
import { apolloHapi, graphiqlHapi, HapiPluginOptions } from './hapiApollo';
|
||||
|
||||
import testSuite, { Schema } from './integrations.test';
|
||||
|
||||
|
@ -12,7 +12,7 @@ function createApp(createOptions: HapiPluginOptions) {
|
|||
});
|
||||
|
||||
server.register({
|
||||
register: ApolloHapi,
|
||||
register: apolloHapi,
|
||||
options: {
|
||||
apolloOptions: createOptions ? createOptions.apolloOptions : { schema: Schema },
|
||||
path: '/graphql',
|
||||
|
@ -20,7 +20,7 @@ function createApp(createOptions: HapiPluginOptions) {
|
|||
});
|
||||
|
||||
server.register({
|
||||
register: GraphiQLHapi,
|
||||
register: graphiqlHapi,
|
||||
options: {
|
||||
path: '/graphiql',
|
||||
graphiqlOptions: {
|
||||
|
|
|
@ -21,7 +21,7 @@ export interface HapiPluginOptions {
|
|||
apolloOptions: ApolloOptions | HapiOptionsFunction;
|
||||
}
|
||||
|
||||
const ApolloHapi: IRegister = function(server: Server, options: HapiPluginOptions, next) {
|
||||
const apolloHapi: IRegister = function(server: Server, options: HapiPluginOptions, next) {
|
||||
server.method('verifyPayload', verifyPayload);
|
||||
server.method('getGraphQLParams', getGraphQLParams);
|
||||
server.method('getApolloOptions', getApolloOptions);
|
||||
|
@ -51,8 +51,8 @@ const ApolloHapi: IRegister = function(server: Server, options: HapiPluginOption
|
|||
path: options.path || '/graphql',
|
||||
config,
|
||||
handler: function(request, reply) {
|
||||
const responses = request.pre.graphQL;
|
||||
if (request.pre.isBatch) {
|
||||
const responses = request.pre['graphQL'];
|
||||
if (request.pre['isBatch']) {
|
||||
return reply(responses);
|
||||
} else {
|
||||
const gqlResponse = responses[0];
|
||||
|
@ -68,7 +68,7 @@ const ApolloHapi: IRegister = function(server: Server, options: HapiPluginOption
|
|||
return next();
|
||||
};
|
||||
|
||||
ApolloHapi.attributes = {
|
||||
apolloHapi.attributes = {
|
||||
name: 'graphql',
|
||||
version: '0.0.1',
|
||||
};
|
||||
|
@ -141,6 +141,7 @@ async function processQuery(graphqlParams, optionsObject: ApolloOptions, reply)
|
|||
validationRules: optionsObject.validationRules,
|
||||
formatError: formatErrorFn,
|
||||
formatResponse: optionsObject.formatResponse,
|
||||
debug: optionsObject.debug,
|
||||
};
|
||||
|
||||
if (optionsObject.formatParams) {
|
||||
|
@ -171,7 +172,7 @@ export interface GraphiQLPluginOptions {
|
|||
graphiqlOptions: GraphiQL.GraphiQLData;
|
||||
}
|
||||
|
||||
const GraphiQLHapi: IRegister = function(server: Server, options: GraphiQLPluginOptions, next) {
|
||||
const graphiqlHapi: IRegister = function(server: Server, options: GraphiQLPluginOptions, next) {
|
||||
server.method('getGraphiQLParams', getGraphiQLParams);
|
||||
server.method('renderGraphiQL', renderGraphiQL);
|
||||
|
||||
|
@ -193,13 +194,13 @@ const GraphiQLHapi: IRegister = function(server: Server, options: GraphiQLPlugi
|
|||
path: options.path || '/graphql',
|
||||
config,
|
||||
handler: (request, reply) => {
|
||||
reply(request.pre.graphiQLString).header('Content-Type', 'text/html');
|
||||
reply(request.pre['graphiQLString']).header('Content-Type', 'text/html');
|
||||
},
|
||||
});
|
||||
next();
|
||||
};
|
||||
|
||||
GraphiQLHapi.attributes = {
|
||||
graphiqlHapi.attributes = {
|
||||
name: 'graphiql',
|
||||
version: '0.0.1',
|
||||
};
|
||||
|
@ -219,8 +220,9 @@ function renderGraphiQL(route, graphiqlParams: any, reply) {
|
|||
query: graphiqlParams.query || graphiqlOptions.query,
|
||||
variables: JSON.parse(graphiqlParams.variables) || graphiqlOptions.variables,
|
||||
operationName: graphiqlParams.operationName || graphiqlOptions.operationName,
|
||||
passHeader: graphiqlOptions.passHeader,
|
||||
});
|
||||
reply(graphiQLString);
|
||||
}
|
||||
|
||||
export { ApolloHapi, GraphiQLHapi };
|
||||
export { apolloHapi, graphiqlHapi };
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
expect,
|
||||
} from 'chai';
|
||||
import { expect } from 'chai';
|
||||
import { stub } from 'sinon';
|
||||
|
||||
import {
|
||||
GraphQLSchema,
|
||||
|
@ -425,6 +424,45 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('sends stack trace to error if debug mode is set', () => {
|
||||
const expected = /at resolveOrError/;
|
||||
const stackTrace = [];
|
||||
const origError = console.error;
|
||||
console.error = (...args) => stackTrace.push(args);
|
||||
app = createApp({apolloOptions: {
|
||||
schema: Schema,
|
||||
debug: true,
|
||||
}});
|
||||
const req = request(app)
|
||||
.post('/graphql')
|
||||
.send({
|
||||
query: 'query test{ testError }',
|
||||
});
|
||||
return req.then((res) => {
|
||||
console.error = origError;
|
||||
return expect(stackTrace[0][0]).to.match(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('sends stack trace to error log if debug mode is set', () => {
|
||||
const logStub = stub(console, 'error');
|
||||
const expected = /at resolveOrError/;
|
||||
app = createApp({apolloOptions: {
|
||||
schema: Schema,
|
||||
debug: true,
|
||||
}});
|
||||
const req = request(app)
|
||||
.post('/graphql')
|
||||
.send({
|
||||
query: 'query test{ testError }',
|
||||
});
|
||||
return req.then((res) => {
|
||||
logStub.restore();
|
||||
expect(logStub.callCount).to.equal(1);
|
||||
return expect(logStub.getCall(0).args[0]).to.match(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('applies additional validationRules', () => {
|
||||
const expected = 'AlwaysInvalidRule was really invalid!';
|
||||
const AlwaysInvalidRule = function (context) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import ApolloOptions from './apolloOptions';
|
|||
import * as GraphiQL from '../modules/renderGraphiQL';
|
||||
|
||||
export interface KoaApolloOptionsFunction {
|
||||
(req: koa.Request): ApolloOptions | Promise<ApolloOptions>;
|
||||
(ctx: koa.Context): ApolloOptions | Promise<ApolloOptions>;
|
||||
}
|
||||
|
||||
export interface KoaHandler {
|
||||
|
@ -25,7 +25,7 @@ export function apolloKoa(options: ApolloOptions | KoaApolloOptionsFunction): Ko
|
|||
let optionsObject: ApolloOptions;
|
||||
if (isOptionsFunction(options)) {
|
||||
try {
|
||||
optionsObject = await options(ctx.request);
|
||||
optionsObject = await options(ctx);
|
||||
} catch (e) {
|
||||
ctx.status = 500;
|
||||
return ctx.body = `Invalid options provided to ApolloServer: ${e.message}`;
|
||||
|
@ -75,6 +75,7 @@ export function apolloKoa(options: ApolloOptions | KoaApolloOptionsFunction): Ko
|
|||
validationRules: optionsObject.validationRules,
|
||||
formatError: formatErrorFn,
|
||||
formatResponse: optionsObject.formatResponse,
|
||||
debug: optionsObject.debug,
|
||||
};
|
||||
|
||||
if (optionsObject.formatParams) {
|
||||
|
@ -118,6 +119,7 @@ export function graphiqlKoa(options: GraphiQL.GraphiQLData) {
|
|||
query: query || options.query,
|
||||
variables: JSON.parse(variables) || options.variables,
|
||||
operationName: operationName || options.operationName,
|
||||
passHeader: options.passHeader,
|
||||
});
|
||||
ctx.set('Content-Type', 'text/html');
|
||||
ctx.body = graphiQLString;
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
* - (optional) variables: a JS object of variables to pre-fill in the GraphiQL UI
|
||||
* - (optional) operationName: the operationName to pre-fill in the GraphiQL UI
|
||||
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI
|
||||
* - (optional) passHeader: a string that will be added to the header object.
|
||||
* For example "'Authorization': localStorage['Meteor.loginToken']" for meteor
|
||||
*/
|
||||
|
||||
export type GraphiQLData = {
|
||||
|
@ -23,6 +25,7 @@ export type GraphiQLData = {
|
|||
variables?: Object,
|
||||
operationName?: string,
|
||||
result?: Object,
|
||||
passHeader?: string
|
||||
};
|
||||
|
||||
// Current latest version of GraphiQL.
|
||||
|
@ -41,6 +44,7 @@ export function renderGraphiQL(data: GraphiQLData): string {
|
|||
data.variables ? JSON.stringify(data.variables, null, 2) : null;
|
||||
const resultString = null;
|
||||
const operationName = data.operationName;
|
||||
const passHeader = data.passHeader ? data.passHeader : '';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
return `
|
||||
|
@ -94,7 +98,7 @@ export function renderGraphiQL(data: GraphiQLData): string {
|
|||
otherParams[k] = parameters[k];
|
||||
}
|
||||
}
|
||||
// We don't use safe-serialize for location, becuase it's not client input.
|
||||
// We don't use safe-serialize for location, because it's not client input.
|
||||
var fetchURL = locationQuery(otherParams, '${endpointURL}');
|
||||
// Defines a GraphQL fetcher using the fetch API.
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
|
@ -102,7 +106,8 @@ export function renderGraphiQL(data: GraphiQLData): string {
|
|||
method: 'post',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
${passHeader}
|
||||
},
|
||||
body: JSON.stringify(graphQLParams),
|
||||
credentials: 'include',
|
||||
|
|
1
src/typings.d.ts
vendored
Normal file
1
src/typings.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="typed-graphql" />
|
26
typings.json
26
typings.json
|
@ -1,25 +1 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"chai": "registry:npm/chai#3.5.0+20160723033700",
|
||||
"graphql": "github:nitintutlani/typed-graphql#ffe7e46e2249cc8f3824a5d15a44938f4354afe9",
|
||||
"http-errors": "registry:npm/http-errors#1.4.0+20160723033700"
|
||||
},
|
||||
"globalDependencies": {
|
||||
"body-parser": "registry:dt/body-parser#0.0.0+20160619023215",
|
||||
"boom": "registry:dt/boom#0.0.0+20160724101333",
|
||||
"connect": "registry:dt/connect#3.4.0+20160317120654",
|
||||
"cookies": "registry:dt/cookies#0.5.1+20160316171810",
|
||||
"express": "registry:dt/express#4.0.0+20160708185218",
|
||||
"express-serve-static-core": "registry:dt/express-serve-static-core#4.0.0+20160805091045",
|
||||
"fibers": "registry:dt/fibers#0.0.0+20160317120654",
|
||||
"hapi": "registry:dt/hapi#13.0.0+20160803202811",
|
||||
"koa": "registry:dt/koa#2.0.0+20160724024233",
|
||||
"koa-bodyparser": "registry:dt/koa-bodyparser#3.0.0+20160414124440",
|
||||
"koa-router": "registry:dt/koa-router#7.0.0+20160314083221",
|
||||
"mime": "registry:dt/mime#0.0.0+20160316155526",
|
||||
"mocha": "registry:dt/mocha#2.2.5+20160720003353",
|
||||
"multer": "registry:dt/multer#0.0.0+20160726135055",
|
||||
"node": "registry:dt/node#6.0.0+20160801161248",
|
||||
"serve-static": "registry:dt/serve-static#0.0.0+20160606155157"
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
|
Loading…
Add table
Reference in a new issue