Core refactor hapi (#36)

* Revert "get supertest working for TS"

This reverts commit 300b32fa5a.

* initial hapi plugin

* working hapi server

* update exports for es6 support
This commit is contained in:
Nick Nance 2016-06-18 10:19:51 -07:00 committed by Jonas Helfer
parent 300b32fa5a
commit f6f25c611e
8 changed files with 90 additions and 46 deletions

View file

@ -41,6 +41,7 @@
"es6-promise": "^3.2.1", "es6-promise": "^3.2.1",
"express": "^4.13.4", "express": "^4.13.4",
"graphql": "^0.6.0", "graphql": "^0.6.0",
"hapi": "^13.4.1",
"source-map-support": "^0.4.0" "source-map-support": "^0.4.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -7,58 +7,51 @@ import {
execute, execute,
} from 'graphql'; } from 'graphql';
import { Promise } from 'es6-promise';
export interface GqlResponse { export interface GqlResponse {
data?: Object; data?: Object;
errors?: Array<string>; errors?: Array<string>;
} }
function runQuery({ export interface QueryOptions {
schema, schema: GraphQLSchema;
query, query: string | Document;
rootValue, rootValue?: any;
context, context?: any;
variables, variables?: { [key: string]: any };
operationName, operationName?: string;
}: { //logFunction?: function => void
schema: GraphQLSchema, //validationRules?: No, too risky. If you want extra validation rules, then parse it yourself.
query: string | Document, }
rootValue?: any,
context?: any, function runQuery(options: QueryOptions): Promise<GraphQLResult> {
variables?: { [key: string]: any },
operationName?: string,
//logFunction?: function => void
//validationRules?: No, too risky. If you want extra validation rules, then parse it yourself.
}): Promise<GraphQLResult> {
let documentAST: Document; let documentAST: Document;
// if query is already an AST, don't parse or validate // if query is already an AST, don't parse or validate
if (typeof query === 'string') { if (typeof options.query === 'string') {
// parse // parse
try { try {
documentAST = parse(query); documentAST = parse(options.query as string);
} catch (syntaxError) { } catch (syntaxError) {
return Promise.resolve({ errors: [syntaxError] }); return Promise.resolve({ errors: [syntaxError] });
} }
// validate // validate
const validationErrors = validate(schema, documentAST); const validationErrors = validate(options.schema, documentAST);
if (validationErrors.length) { if (validationErrors.length) {
return Promise.resolve({ errors: validationErrors }); return Promise.resolve({ errors: validationErrors });
} }
} else { } else {
documentAST = query; documentAST = options.query as Document;
} }
// execute // execute
return execute( return execute(
schema, options.schema,
documentAST, documentAST,
rootValue, options.rootValue,
context, options.context,
variables, options.variables,
operationName options.operationName
); );
} }

View file

@ -1,3 +1,2 @@
import expressApollo from './integrations/expressApollo'; export { graphqlHTTP } from './integrations/expressApollo';
export { HapiApollo } from './integrations/hapiApollo';
export { expressApollo };

View file

@ -11,7 +11,8 @@ import {
// TODO use import, not require... help appreciated. // TODO use import, not require... help appreciated.
import * as express from 'express'; import * as express from 'express';
import request from 'supertest-as-promised'; // tslint:disable-next-line
const request = require('supertest-as-promised');
import { graphqlHTTP, ExpressApolloOptions, renderGraphiQL } from './expressApollo'; import { graphqlHTTP, ExpressApolloOptions, renderGraphiQL } from './expressApollo';
@ -52,9 +53,7 @@ describe('expressApollo', () => {
}; };
return request(app).get( return request(app).get(
'/graphql?query={ testString }' '/graphql?query={ testString }'
) ).then((res) => {
.expect(200)
.then((res) => {
return expect(res.body.data).to.deep.equal(expected); return expect(res.body.data).to.deep.equal(expected);
}); });
}); });

View file

@ -2,7 +2,7 @@ import * as express from 'express';
import * as graphql from 'graphql'; import * as graphql from 'graphql';
import { runQuery } from '../core/runQuery'; import { runQuery } from '../core/runQuery';
import { renderGraphiQL, GraphiQLData } from '../modules/renderGraphiQL'; import * as GraphiQL from '../modules/renderGraphiQL';
// TODO: will these be the same or different for other integrations? // TODO: will these be the same or different for other integrations?
export interface ExpressApolloOptions { export interface ExpressApolloOptions {
@ -15,7 +15,11 @@ export interface ExpressApolloOptions {
// answer: yes, it does. Func(req) => options // answer: yes, it does. Func(req) => options
} }
export function graphqlHTTP(options: ExpressApolloOptions) { export interface ExpressHandler {
(req: express.Request, res: express.Response, next): void;
}
export function graphqlHTTP(options: ExpressApolloOptions): ExpressHandler {
if (!options) { if (!options) {
throw new Error('Apollo graphqlHTTP middleware requires options.'); throw new Error('Apollo graphqlHTTP middleware requires options.');
} }
@ -46,9 +50,9 @@ function getQueryString(req: express.Request): string {
// this returns the html for the GraphiQL interactive query UI // this returns the html for the GraphiQL interactive query UI
// TODO: it's still missing a way to tell it where the GraphQL endpoint is. // TODO: it's still missing a way to tell it where the GraphQL endpoint is.
export function renderGraphiQL(options: GraphiQLData) { export function renderGraphiQL(options: GraphiQL.GraphiQLData) {
return (req: express.Request, res: express.Response, next) => { return (req: express.Request, res: express.Response, next) => {
const graphiQLString = renderGraphiQL({ const graphiQLString = GraphiQL.renderGraphiQL({
query: options.query, query: options.query,
variables: options.variables, variables: options.variables,
operationName: options.operationName, operationName: options.operationName,

View file

@ -0,0 +1,52 @@
import * as hapi from 'hapi';
import * as graphql from 'graphql';
import { runQuery } from '../core/runQuery';
export interface IRegister {
(server: hapi.Server, options: any, next: any): void;
attributes?: any;
}
export interface HapiApolloOptions {
schema: graphql.GraphQLSchema;
formatError?: Function;
rootValue?: any;
context?: any;
logFunction?: Function;
}
export class HapiApollo {
constructor() {
this.register.attributes = {
name: 'graphql',
version: '0.0.1',
};
}
public register: IRegister = (server: hapi.Server, options: HapiApolloOptions, next) => {
server.route({
method: 'GET',
path: '/test',
handler: (request, reply) => {
reply('test passed');
},
});
server.route({
method: 'POST',
path: '/',
handler: (request, reply) => {
return runQuery({
schema: options.schema,
query: request.payload,
}).then(gqlResponse => {
reply({ data: gqlResponse.data });
}).catch(errors => {
reply({ errors: errors }).code(500);
});
},
});
next();
}
}

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es6",
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"sourceMap": true, "sourceMap": true,

View file

@ -4,18 +4,14 @@
"graphql": "github:nitintutlani/typed-graphql" "graphql": "github:nitintutlani/typed-graphql"
}, },
"globalDependencies": { "globalDependencies": {
"bluebird": "registry:dt/bluebird#2.0.0+20160319051630",
"body-parser": "registry:dt/body-parser#0.0.0+20160317120654", "body-parser": "registry:dt/body-parser#0.0.0+20160317120654",
"es6-promise": "registry:dt/es6-promise#0.0.0+20160317120654",
"express": "registry:dt/express#4.0.0+20160317120654", "express": "registry:dt/express#4.0.0+20160317120654",
"express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160322035842", "express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160322035842",
"hapi": "registry:dt/hapi#13.0.0+20160521152637", "hapi": "registry:dt/hapi#13.0.0+20160602125023",
"koa": "registry:dt/koa#2.0.0+20160317120654", "koa": "registry:dt/koa#2.0.0+20160317120654",
"mime": "registry:dt/mime#0.0.0+20160316155526", "mime": "registry:dt/mime#0.0.0+20160316155526",
"mocha": "registry:dt/mocha#2.2.5+20160317120654", "mocha": "registry:dt/mocha#2.2.5+20160317120654",
"node": "registry:dt/node#6.0.0+20160524002506", "node": "registry:dt/node#6.0.0+20160524002506",
"serve-static": "registry:dt/serve-static#0.0.0+20160501131543", "serve-static": "registry:dt/serve-static#0.0.0+20160501131543"
"superagent": "registry:dt/superagent#1.4.0+20160317120654",
"supertest-as-promised": "registry:dt/supertest-as-promised#2.0.2+20160317120654"
} }
} }