This commit is contained in:
unicodeveloper 2018-07-13 10:25:42 -07:00
commit a31501ff66
63 changed files with 871 additions and 359 deletions

View file

@ -70,6 +70,8 @@ redirects:
docs/guides/performance
/docs/apollo-server/v2/features/apq.html:
docs/guides/performance
/docs/apollo-server/v2/features/file-uploads.html:
docs/guides/file-uploads
versioned-netlify-redirects:
netlify_site_id: apollo-server-docs

View file

@ -6,7 +6,7 @@
"version": "3.7.1"
},
"devDependencies": {
"apollo-hexo-config": "1.0.7",
"apollo-hexo-config": "1.0.8",
"chexo": "1.0.5",
"hexo": "3.7.1",
"hexo-prism-plus": "1.0.0",

View file

@ -49,6 +49,10 @@ new ApolloServer({
Enables and disables schema introspection
* `playground`: <`Boolean`> | <`Object`>
Enables and disables playground and allows configuration of GraphQL Playground. The options can be found on GraphQL Playground's [documentation](https://github.com/prismagraphql/graphql-playground/#usage)
* `debug`: <`Boolean`>
Enables and disables development mode helpers. Defaults to `true`
@ -61,13 +65,13 @@ new ApolloServer({
Add tracing or cacheControl meta data to the GraphQL response
* `formatError`, `formatResponse`, `formatParams`: <`Function`>
* `formatError`, `formatResponse`: <`Function`>
Functions to format the errors and response returned from the server, as well as the parameters to graphql execution(`runQuery`)
* `schema`: <`Object`>
An executable GraphQL schema that will override the `typeDefs` and `resolvers` provided
An executable GraphQL schema that will override the `typeDefs` and `resolvers` provided. If you are using [file uploads](https://www.apollographql.com/docs/guides/file-uploads.html), you will have to add the `Upload` scalar to the schema, as it is not automatically added in case of setting the `schema` manually.
* `subscriptions`: <`Object`> | <`String`> | false
@ -86,6 +90,10 @@ new ApolloServer({
The persisted queries option can be set to an object containing a `cache` field, which will be used to store the mapping between hash and query string.
* `cors`: <`Object` | `boolean`> ([apollo-server](https://github.com/expressjs/cors#cors))
Pass the integration-specific CORS options. `false` removes the CORS middleware and `true` uses the defaults. This option is only available to `apollo-server`. For other server integrations, place `cors` inside of `applyMiddleware`.
#### Returns
`ApolloServer`

View file

@ -60,6 +60,7 @@ type ExampleType {
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective
}
@ -419,26 +420,28 @@ One drawback of this approach is that it does not guarantee fields will be wrapp
Suppose you want to enforce a maximum length for a string-valued field:
```js
const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server");
const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { GraphQLScalarType, GraphQLNonNull } = require('graphql');
const typeDefs = gql`
directive @length(max: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @length(max: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
type Query {
books: [Book]
}
type Query {
books: [Book]
}
type Book {
title: String @length(max: 50)
}
type Book {
title: String @length(max: 50)
}
type Mutation {
createBook(book: BookInput): Book
}
type Mutation {
createBook(book: BookInput): Book
}
input BookInput {
title: String! @length(max: 50)
}`;
input BookInput {
title: String! @length(max: 50)
}
`;
class LengthDirective extends SchemaDirectiveVisitor {
visitInputFieldDefinition(field) {
@ -452,10 +455,13 @@ class LengthDirective extends SchemaDirectiveVisitor {
// Replace field.type with a custom GraphQLScalarType that enforces the
// length restriction.
wrapType(field) {
if (field.type instanceof GraphQLNonNull &&
field.type.ofType instanceof GraphQLScalarType) {
if (
field.type instanceof GraphQLNonNull &&
field.type.ofType instanceof GraphQLScalarType
) {
field.type = new GraphQLNonNull(
new LimitedLengthType(field.type.ofType, this.args.max));
new LimitedLengthType(field.type.ofType, this.args.max),
);
} else if (field.type instanceof GraphQLScalarType) {
field.type = new LimitedLengthType(field.type, this.args.max);
} else {
@ -477,7 +483,7 @@ class LimitedLengthType extends GraphQLScalarType {
value = type.serialize(value);
assert.isAtMost(value.length, maxLength);
return value;
}
},
parseValue(value) {
return type.parseValue(value);
@ -485,16 +491,17 @@ class LimitedLengthType extends GraphQLScalarType {
parseLiteral(ast) {
return type.parseLiteral(ast);
}
},
});
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
length: LengthDirective
}
length: LengthDirective,
},
});
server.listen().then(({ url }) => {
@ -560,6 +567,7 @@ class UniqueIdDirective extends SchemaDirectiveVisitor {
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
uniqueID: UniqueIdDirective
}

View file

@ -40,16 +40,12 @@ Apollo Server provides two ways to log a server: per input, response, and errors
### High Level Logging
To log the inputs, response, and request, Apollo Server provides three methods: `formatParams`, `formatError`, and `formatResponse`. This example uses `console.log` to record the information, servers can use other more sophisticated tools.
To log, Apollo Server provides: `formatError` and `formatResponse`. This example uses `console.log` to record the information, servers can use other more sophisticated tools.
```js
const server = new ApolloServer({
typeDefs,
resolvers,
formatParams: params => {
console.log(params);
return params;
},
formatError: error => {
console.log(error);
return error;
@ -67,7 +63,7 @@ server.listen().then(({ url }) => {
### Granular Logs
Additionally for more advanced cases, Apollo Server accepts an array of `graphql-extensions` to the `extensions` field. These extensions receive a variety of lifecycle calls for each phase of a GraphQL request and can keep state, such as the request headers.
For more advanced cases, Apollo Server provides an experimental api that accepts an array of `graphql-extensions` to the `extensions` field. These extensions receive a variety of lifecycle calls for each phase of a GraphQL request and can keep state, such as the request headers.
```js
const { ApolloServer } = require('apollo-server');

View file

@ -0,0 +1,53 @@
---
title: GraphQL Playground
description: Visually exploring a Apollo Server
---
[GraphQL Playground](https://github.com/prismagraphql/graphql-playground) is a graphical interactive in-browser GraphQL IDE, created by [Prisma](https://www.prisma.io/), based on [GraphiQL](https://github.com/graphql/graphiql). In development, Apollo Server collocates a GraphQL Playground instance with the GraphQL path. When a browser sends a request to Apollo Server, it receives GraphQL Playground. When `NODE_ENV` is set to production, introspection and Playground are disabled as a production best practice.
<div align="center">
![GraphQL Playground](../images/playground.png)
</div>
## Configuring Playground
The Apollo Server constructor contains the ability to configure GraphQL Playground with the `playground` configuration option. The options can be found on GraphQL Playground's [documentation](https://github.com/prismagraphql/graphql-playground/#usage)
```js
new ApolloServer({
typeDefs,
resolvers,
playground: {
settings: {
'editor.theme': 'light',
},
tabs: [
{
endpoint,
query: defaultQuery,
},
],
},
});
```
## Enabling Playground in Production
To enable Playground in production, introspection and the playground can be enabled explicitly in the following manner.
```js line=7-8
const { ApolloServer } = require('apollo-server');
const { typeDefs, resolvers } = require('./schema');
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: true,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
```

View file

@ -5,11 +5,11 @@ subtitle: Adding subscriptions to Apollo Server
Subscriptions are GraphQL operations that watch events emitted from Apollo Server.
The native Apollo Server supports GraphQL subscriptions without additional configuration.
All integration that allow HTTP servers, such as express and Hapi, also provide GraphQL subscriptions.
All integrations that allow HTTP servers, such as express and Hapi, also provide GraphQL subscriptions.
## Subscriptions Example
Subscriptions depend on use a publish and subscribe primitive to generate the events that notify a subscription. `PubSub` is a factory that creates event generators that is provided by all supported packages. `PubSub` is an implementation of the `PubSubEngine` interface, which has been adopted by a variety of additional [event-generating backends](#PubSub-Implementations).
Subscriptions depend on use of a publish and subscribe primitive to generate the events that notify a subscription. `PubSub` is a factory that creates event generators that is provided by all supported packages. `PubSub` is an implementation of the `PubSubEngine` interface, which has been adopted by a variety of additional [event-generating backends](#PubSub-Implementations).
```js
const { PubSub } = require('apollo-server');

View file

@ -243,13 +243,9 @@ new ApolloServer({
});
```
For most other functions that might have required access to the middleware arguments, such as `formatParams`, `formatError`, and `formatResponse`, it is possible to create a `graphql-extension`.
For more complicated use cases, the `ApolloServer` class can be extended to override the `createGraphQLServerOptions` function to create parameters based on the same argument that's passed to the context.
<h2 id="log-function">Replacing `logFunction`</h2>
Apollo Server 2 removes the `logFunction` in favor of using `graphql-extensions`, which provides a more structured and flexible way of instrumenting Apollo Server. The explanation of how to do this more granular logging, can be found in the [metrics section](./features/metrics.html)
Apollo Server 2 removes the `logFunction` to reduce the exposure of internal implementation details. The experimental, non-public `graphql-extensions` provides a more structured and flexible way of instrumenting Apollo Server. An explanation of to do more granular logging, can be found in the [metrics section](./features/metrics.html).
<h2 id="graphiql">Replacing GraphiQL</h2>

View file

@ -100,9 +100,6 @@ The above are the only options you need most of the time. Here are some others t
```js
// options object
const GraphQLOptions = {
// a function applied to the parameters of every invocation of runQuery
formatParams?: Function,
// * - (optional) validationRules: extra validation rules applied to requests
validationRules?: Array<ValidationRule>,

View file

@ -2,7 +2,7 @@
title: What's new?
---
This section of the Apollo Server docs is an announcement page where it is easy to find and share big changes to the ApolloServer package, or the Apollo server side ecosystem. For a more detailed list of changes, check out the [Changelog](https://github.com/apollographql/apollo-server/blob/version-2/CHANGELOG.md).
This section of the Apollo Server docs is an announcement page where it is easy to find and share big changes to the ApolloServer package, or the Apollo server side ecosystem. For a more detailed list of changes, check out the [Changelog](https://github.com/apollographql/apollo-server/blob/version-2/CHANGELOG.md). To upgrade from Apollo Server 1, please follow the [migration guide](./migration-two-dot.html)
## 2.0
@ -42,7 +42,35 @@ server.listen().then(({ url }) => {
This is just the beginning. We have published a [roadmap](https://github.com/apollographql/apollo-server/blob/master/ROADMAP.md) for all of the features we will be bringing to Apollo Server soon and we would love your help! If you have any interest, you can get involved on [Github](https://github.com/apollographql/apollo-server) or by joining the [Apollo Slack](https://www.apollographql.com/slack) and going to the #apollo-server channel.
## [Errors](./features/errors.html)
## Automatic Persisted Queries ([guide](https://www.apollographql.com/docs/guides/performance.html#Automatic-Persisted-Queries))
A persisted query is an ID or hash that can be sent to the server in place of the GraphQL query string. This smaller signature reduces bandwidth utilization and speeds up client loading times. Apollo Server enables persisted queries without additional server configuration, using an in-memory LRU cache to store the mapping between hash and query string. The persisted query cache can be configured as shown in the following code snippet. To enable persisted queries on the client, follow the [Performance Guide](https://www.apollographql.com/docs/guides/performance.html#Automatic-Persisted-Queries).
```js line=7-12
const { ApolloServer } = require("apollo-server");
const { MemcachedCache } = require('apollo-server-memcached');
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
cache: new MemcachedCache(
['memcached-server-1', 'memcached-server-2', 'memcached-server-3'],
{ retries: 10, retry: 10000 }, // Options
),
},
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
```
## CDN Integration ([guide](https://www.apollographql.com/docs/guides/performance.html#CDN-Integration))
Apollo Server works well with a Content-Distribution Network to cache full GraphQL query results. Apollo Server provides `cache-control` headers that a CDN uses to determine how long a request should be cached. For subsequent requests, the result will be served directly from the CDN's cache. A CDN paired with Apollo Server's persisted queries is especially powerful, since GraphQL operations can be shortened and sent with a HTTP GET request. To enable caching and a CDN in Apollo Server, follow the [Performance Guide](https://www.apollographql.com/docs/guides/performance.html#CDN-Integration).
## [Apollo Errors](./features/errors.html)
Apollo Server provides the ability to add error codes to categorize errors that occur within resolvers. In addition to an error code, Apollo Server 2 passes error stack traces in development mode to enable a smoother getting started experience.
@ -114,32 +142,6 @@ server.listen().then(({ url }) => {
});
```
## Health Checks
The default Apollo server provides a health check endpoint at `/.well-known/apollo/server-health` that returns a 200 status code by default. If `onHealthCheck` is defined, the promise returned from the callback determines the status code. A successful resolution causes a 200 and rejection causes a 503. Health checks are often used by load balancers to determine if a server is available.
```js
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql``;
const resolvers = {};
const server = new ApolloServer({
typeDefs,
resolvers,
//optional parameter
onHealthCheck: () => new Promise((resolve, reject) => {
//database check or other asynchronous action
}),
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
console.log(`Try your health check at: ${url}.well-known/apollo/server-health`);
});
```
## [Performance Monitoring](./features/metrics.html)
Apollo Server 2.0 enables GraphQL monitoring out of the box. It reports performance and error data out-of-band to Apollo Engine. And Apollo Engine displays information about every query and schema present in your GraphQL service.
@ -169,7 +171,6 @@ server.listen().then(({ url }) => {
});
```
## [GraphQL Playground](./features/playground.html)
Apollo Server 2.0 creates a single GraphQL endpoint that provides data and a gui explorer depending on how the endpoint is accessed. In browser, Apollo Server returns GraphQL playground. For other cases, Apollo server returns the data for GraphQL requests from other clients, such as Apollo Client, curl, Postman, or Insomnia.
@ -303,3 +304,29 @@ setInterval(
```
> Note: to disable subscriptions, set `subscriptions` to `false` in the options passed to `listen`
## Health Checks
The default Apollo server provides a health check endpoint at `/.well-known/apollo/server-health` that returns a 200 status code by default. If `onHealthCheck` is defined, the promise returned from the callback determines the status code. A successful resolution causes a 200 and rejection causes a 503. Health checks are often used by load balancers to determine if a server is available.
```js
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql``;
const resolvers = {};
const server = new ApolloServer({
typeDefs,
resolvers,
//optional parameter
onHealthCheck: () => new Promise((resolve, reject) => {
//database check or other asynchronous action
}),
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
console.log(`Try your health check at: ${url}.well-known/apollo/server-health`);
});
```

View file

@ -1,6 +1,6 @@
{
"name": "apollo-cache-control",
"version": "0.1.1",
"version": "0.2.0-rc.0",
"description": "A GraphQL extension for cache control",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@ -18,7 +18,7 @@
"node": ">=6.0"
},
"dependencies": {
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.7",
"graphql-extensions": "0.1.0-beta.0"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-datasource-rest",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
@ -23,15 +23,14 @@
"node": ">=6"
},
"dependencies": {
"apollo-server-caching": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-errors": "^2.0.0-rc.6",
"http-cache-semantics": "^4.0.0",
"lru-cache": "^4.1.3"
"apollo-datasource": "^2.0.0-rc.7",
"apollo-server-caching": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7",
"apollo-server-errors": "^2.0.0-rc.7",
"http-cache-semantics": "^4.0.0"
},
"devDependencies": {
"@types/jest": "^23.1.2",
"@types/lru-cache": "^4.1.1",
"jest": "^23.2.0",
"ts-jest": "^22.4.6"
},

View file

@ -78,7 +78,7 @@ export class HTTPCache {
const body = await response.text();
const entry = JSON.stringify({ policy: policy.toObject(), body });
let ttl = policy.timeToLive() / 1000;
let ttl = Math.round(policy.timeToLive() / 1000);
// If a response can be revalidated, we don't want to remove it from the cache right after it expires.
// We may be able to use better heuristics here, but for now we'll take the max-age times 2.
if (canBeRevalidated(response)) {

View file

@ -9,7 +9,11 @@ import {
URLSearchParamsInit,
} from 'apollo-server-env';
import { DataSource } from 'apollo-datasource';
import { KeyValueCache } from 'apollo-server-caching';
import { HTTPCache } from './HTTPCache';
import {
ApolloError,
AuthenticationError,
@ -28,10 +32,15 @@ export { Request };
type ValueOrPromise<T> = T | Promise<T>;
export abstract class RESTDataSource<TContext = any> {
export abstract class RESTDataSource<TContext = any> extends DataSource {
httpCache!: HTTPCache;
context!: TContext;
initialize(context: TContext, cache: KeyValueCache): void {
this.context = context;
this.httpCache = new HTTPCache(cache);
}
baseURL?: string;
protected willSendRequest?(request: RequestOptions): ValueOrPromise<void>;
@ -48,22 +57,56 @@ export abstract class RESTDataSource<TContext = any> {
}
}
protected async didReceiveErrorResponse<TResult = any>(
protected async didReceiveResponse<TResult = any>(
response: Response,
_request: Request,
): Promise<TResult> {
const message = `${response.status} ${
response.statusText
}: ${await response.text()}`;
if (response.status === 401) {
throw new AuthenticationError(message);
} else if (response.status === 403) {
throw new ForbiddenError(message);
if (response.ok) {
return (this.parseBody(response) as any) as Promise<TResult>;
} else {
throw new ApolloError(message);
throw await this.errorFromResponse(response);
}
}
protected didEncounterError(error: Error, _request: Request) {
throw error;
}
protected parseBody(response: Response): Promise<object | string> {
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.startsWith('application/json')) {
return response.json();
} else {
return response.text();
}
}
protected async errorFromResponse(response: Response) {
const message = `${response.status}: ${response.statusText}`;
let error: ApolloError;
if (response.status === 401) {
error = new AuthenticationError(message);
} else if (response.status === 403) {
error = new ForbiddenError(message);
} else {
error = new ApolloError(message);
}
const body = await this.parseBody(response);
Object.assign(error.extensions, {
response: {
url: response.url,
status: response.status,
statusText: response.statusText,
body,
},
});
return error;
}
protected async get<TResult = any>(
path: string,
params?: URLSearchParamsInit,
@ -144,8 +187,10 @@ export abstract class RESTDataSource<TContext = any> {
// We accept arbitrary objects as body and serialize them as JSON
if (
options.body !== undefined &&
typeof options.body !== 'string' &&
!(options.body instanceof ArrayBuffer)
options.body !== null &&
(options.body.constructor === Object ||
((options.body as any).toJSON &&
typeof (options.body as any).toJSON === 'function'))
) {
options.body = JSON.stringify(options.body);
options.headers.set('Content-Type', 'application/json');
@ -154,17 +199,11 @@ export abstract class RESTDataSource<TContext = any> {
const request = new Request(String(url), options);
return this.trace(`${options.method || 'GET'} ${url}`, async () => {
const response = await this.httpCache.fetch(request);
if (response.ok) {
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.startsWith('application/json')) {
return response.json();
} else {
return response.text();
}
} else {
return this.didReceiveErrorResponse(response);
try {
const response = await this.httpCache.fetch(request);
return this.didReceiveResponse(response, request);
} catch (error) {
this.didEncounterError(error, request);
}
});
}

View file

@ -278,7 +278,7 @@ describe('RESTDataSource', () => {
);
});
it('allows passing in a request body', async () => {
it('serializes a request body that is an object as JSON', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
@ -301,6 +301,66 @@ describe('RESTDataSource', () => {
);
});
it('serializes a request body that has a toJSON method as JSON', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
postFoo(foo) {
return this.post('foo', foo);
}
}();
dataSource.httpCache = httpCache;
fetch.mockJSONResponseOnce();
class Model {
constructor(public baz: any) {}
toJSON() {
return {
foo: this.baz,
};
}
}
const model = new Model('bar');
await dataSource.postFoo(model);
expect(fetch.mock.calls.length).toEqual(1);
expect(fetch.mock.calls[0][0].url).toEqual('https://api.example.com/foo');
expect(fetch.mock.calls[0][0].body).toEqual(JSON.stringify({ foo: 'bar' }));
expect(fetch.mock.calls[0][0].headers.get('Content-Type')).toEqual(
'application/json',
);
});
it('does not serialize a request body that is not an object', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
postFoo(foo) {
return this.post('foo', foo);
}
}();
dataSource.httpCache = httpCache;
fetch.mockJSONResponseOnce();
class FormData {}
const form = new FormData();
await dataSource.postFoo(form);
expect(fetch.mock.calls.length).toEqual(1);
expect(fetch.mock.calls[0][0].url).toEqual('https://api.example.com/foo');
expect(fetch.mock.calls[0][0].body).not.toEqual('{}');
expect(fetch.mock.calls[0][0].headers.get('Content-Type')).not.toEqual(
'application/json',
);
});
for (const method of ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']) {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
@ -340,51 +400,123 @@ describe('RESTDataSource', () => {
});
}
it('throws an AuthenticationError when the response status is 401', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
describe('error handling', () => {
it('throws an AuthenticationError when the response status is 401', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
getFoo() {
return this.get('foo');
}
}();
getFoo() {
return this.get('foo');
}
}();
dataSource.httpCache = httpCache;
dataSource.httpCache = httpCache;
fetch.mockResponseOnce('Invalid token', undefined, 401);
fetch.mockResponseOnce('Invalid token', undefined, 401);
await expect(dataSource.getFoo()).rejects.toThrow(AuthenticationError);
});
const result = dataSource.getFoo();
await expect(result).rejects.toThrow(AuthenticationError);
await expect(result).rejects.toMatchObject({
extensions: {
code: 'UNAUTHENTICATED',
response: {
status: 401,
body: 'Invalid token',
},
},
});
});
it('throws a ForbiddenError when the response status is 403', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
it('throws a ForbiddenError when the response status is 403', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
getFoo() {
return this.get('foo');
}
}();
getFoo() {
return this.get('foo');
}
}();
dataSource.httpCache = httpCache;
dataSource.httpCache = httpCache;
fetch.mockResponseOnce('No access', undefined, 403);
fetch.mockResponseOnce('No access', undefined, 403);
await expect(dataSource.getFoo()).rejects.toThrow(ForbiddenError);
});
const result = dataSource.getFoo();
await expect(result).rejects.toThrow(ForbiddenError);
await expect(result).rejects.toMatchObject({
extensions: {
code: 'FORBIDDEN',
response: {
status: 403,
body: 'No access',
},
},
});
});
it('throws an ApolloError when the response status is 500', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
it('throws an ApolloError when the response status is 500', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
getFoo() {
return this.get('foo');
}
}();
getFoo() {
return this.get('foo');
}
}();
dataSource.httpCache = httpCache;
dataSource.httpCache = httpCache;
fetch.mockResponseOnce('Oops', undefined, 500);
fetch.mockResponseOnce('Oops', undefined, 500);
await expect(dataSource.getFoo()).rejects.toThrow(ApolloError);
const result = dataSource.getFoo();
await expect(result).rejects.toThrow(ApolloError);
await expect(result).rejects.toMatchObject({
extensions: {
response: {
status: 500,
body: 'Oops',
},
},
});
});
it('puts JSON error responses on the error as an object', async () => {
const dataSource = new class extends RESTDataSource {
baseURL = 'https://api.example.com';
getFoo() {
return this.get('foo');
}
}();
dataSource.httpCache = httpCache;
fetch.mockResponseOnce(
JSON.stringify({
errors: [
{
message: 'Houston, we have a problem.',
},
],
}),
{ 'Content-Type': 'application/json' },
500,
);
const result = dataSource.getFoo();
await expect(result).rejects.toThrow(ApolloError);
await expect(result).rejects.toMatchObject({
extensions: {
response: {
status: 500,
body: {
errors: [
{
message: 'Houston, we have a problem.',
},
],
},
},
},
});
});
});
});

View file

@ -1,3 +1,2 @@
export { RESTDataSource, RequestOptions } from './RESTDataSource';
export { HTTPCache } from './HTTPCache';
export { KeyValueCache, InMemoryLRUCache } from 'apollo-server-caching';

View file

@ -0,0 +1,6 @@
*
!src/**/*
!dist/**/*
dist/**/*.test.*
!package.json
!README.md

View file

@ -0,0 +1,57 @@
{
"name": "apollo-datasource",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-datasource"
},
"homepage": "https://github.com/apollographql/apollo-server#readme",
"bugs": {
"url": "https://github.com/apollographql/apollo-server/issues"
},
"scripts": {
"clean": "rm -rf dist",
"compile": "tsc",
"prepublish": "npm run clean && npm run compile",
"test": "jest --verbose"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"engines": {
"node": ">=6"
},
"dependencies": {
"apollo-server-caching": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7"
},
"devDependencies": {
"@types/jest": "^23.1.2",
"jest": "^23.2.0",
"ts-jest": "^22.4.6"
},
"jest": {
"testEnvironment": "node",
"setupFiles": [
"<rootDir>/node_modules/apollo-server-env/dist/index.js"
],
"transform": {
"^.+\\.(ts|js)$": "ts-jest"
},
"moduleFileExtensions": [
"ts",
"js",
"json"
],
"testRegex": "apollo-datasource-rest/src/__tests__/.*$",
"roots": [
"../../"
],
"globals": {
"ts-jest": {
"skipBabel": true
}
}
}
}

View file

@ -0,0 +1,5 @@
import { KeyValueCache } from 'apollo-server-caching';
export abstract class DataSource<TContext = any> {
abstract initialize(context: TContext, cache: KeyValueCache): void;
}

View file

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"removeComments": true,
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"types": ["node", "jest"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"]
}

View file

@ -1,6 +1,6 @@
{
"name": "apollo-engine-reporting",
"version": "0.0.0-rc.0",
"version": "0.0.0-rc.1",
"description": "Send reports about your GraphQL services to Apollo Engine",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@ -22,9 +22,9 @@
},
"dependencies": {
"apollo-engine-reporting-protobuf": "0.0.0-beta.7",
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.7",
"async-retry": "^1.2.1",
"graphql-extensions": "^0.1.0-rc.0",
"graphql-extensions": "^0.1.0-rc.1",
"lodash": "^4.17.10"
},
"devDependencies": {

View file

@ -79,6 +79,8 @@ export interface EngineReportingOptions {
// 'sendReport()' on other signals if you'd like. Note that 'sendReport()'
// does not run synchronously so it cannot work usefully in an 'exit' handler.
handleSignals?: boolean;
// Sends the trace report immediately. This options is useful for stateless environments
sendReportsImmediately?: boolean;
// XXX Provide a way to set client_name, client_version, client_address,
// service, and service_version fields. They are currently not revealed in the
@ -103,6 +105,8 @@ export class EngineReportingAgent<TContext = any> {
private report!: FullTracesReport;
private reportSize!: number;
private reportTimer: any; // timer typing is weird and node-specific
private sendReportsImmediately?: boolean;
private stopped: boolean = false;
public constructor(options: EngineReportingOptions = {}) {
this.options = options;
@ -115,10 +119,13 @@ export class EngineReportingAgent<TContext = any> {
this.resetReport();
this.reportTimer = setInterval(
() => this.sendReportAndReportErrors(),
this.options.reportIntervalMs || 10 * 1000,
);
this.sendReportsImmediately = options.sendReportsImmediately;
if (!this.sendReportsImmediately) {
this.reportTimer = setInterval(
() => this.sendReportAndReportErrors(),
this.options.reportIntervalMs || 10 * 1000,
);
}
if (this.options.handleSignals !== false) {
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];
@ -141,7 +148,7 @@ export class EngineReportingAgent<TContext = any> {
public addTrace(signature: string, operationName: string, trace: Trace) {
// Ignore traces that come in after stop().
if (!this.reportTimer) {
if (this.stopped) {
return;
}
@ -165,8 +172,9 @@ export class EngineReportingAgent<TContext = any> {
// If the buffer gets big (according to our estimate), send.
if (
this.sendReportsImmediately ||
this.reportSize >=
(this.options.maxUncompressedReportSize || 4 * 1024 * 1024)
(this.options.maxUncompressedReportSize || 4 * 1024 * 1024)
) {
this.sendReportAndReportErrors();
}
@ -270,6 +278,8 @@ export class EngineReportingAgent<TContext = any> {
clearInterval(this.reportTimer);
this.reportTimer = undefined;
}
this.stopped = true;
}
private sendReportAndReportErrors(): Promise<void> {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-caching",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
@ -20,5 +20,11 @@
"types": "dist/index.d.ts",
"engines": {
"node": ">=6"
},
"dependencies": {
"lru-cache": "^4.1.3"
},
"devDependencies": {
"@types/lru-cache": "^4.1.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-cloudflare",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Production-ready Node.js GraphQL server for Cloudflare workers",
"main": "dist/index.js",
"scripts": {
@ -24,8 +24,8 @@
},
"homepage": "https://github.com/apollographql/apollo-server#readme",
"dependencies": {
"apollo-server-core": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.6"
"apollo-server-core": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-core",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Core engine for Apollo GraphQL server",
"main": "dist/index.js",
"scripts": {
@ -29,22 +29,20 @@
},
"dependencies": {
"@types/ws": "^5.1.2",
"apollo-cache-control": "^0.1.1",
"apollo-datasource-rest": "^2.0.0-rc.6",
"apollo-engine-reporting": "^0.0.0-rc.0",
"apollo-server-caching": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-errors": "^2.0.0-rc.6",
"apollo-tracing": "^0.2.0-beta.1",
"apollo-cache-control": "^0.2.0-rc.0",
"apollo-datasource": "^2.0.0-rc.7",
"apollo-engine-reporting": "^0.0.0-rc.1",
"apollo-server-caching": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7",
"apollo-server-errors": "^2.0.0-rc.7",
"apollo-tracing": "^0.2.0-rc.0",
"apollo-upload-server": "^5.0.0",
"graphql-extensions": "^0.1.0-rc.0",
"graphql-extensions": "^0.1.0-rc.1",
"graphql-subscriptions": "^0.5.8",
"graphql-tag": "^2.9.2",
"graphql-tools": "^3.0.4",
"hash.js": "^1.1.3",
"keyv": "^3.0.0",
"lodash": "^4.17.10",
"quick-lru": "^1.1.0",
"subscriptions-transport-ws": "^0.9.11",
"ws": "^5.2.0"
},

View file

@ -21,10 +21,6 @@ import {
ExecutionParams,
} from 'subscriptions-transport-ws';
// use as default persisted query store
import Keyv = require('keyv');
import QuickLru = require('quick-lru');
import { formatApolloErrors } from 'apollo-server-errors';
import {
GraphQLServerOptions as GraphQLOptions,
@ -42,7 +38,11 @@ import {
import { FormatErrorExtension } from './formatters';
import { gql } from './index';
import { PersistedQueryCache } from './caching';
import {
createPlaygroundOptions,
PlaygroundRenderPageOptions,
} from './playground';
const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
@ -69,14 +69,12 @@ export class ApolloServerBase {
protected subscriptionServerOptions?: SubscriptionServerOptions;
protected uploadsConfig?: FileUploadOptions;
// This specifies the version of GraphQL Playground that will be served
// from graphql-playground-html, and is passed to renderPlaygroundPage
// by the integration subclasses
protected playgroundVersion = '1.7.1';
// set by installSubscriptionHandlers.
private subscriptionServer?: SubscriptionServer;
// the default version is specified in playground.ts
protected playgroundOptions?: PlaygroundRenderPageOptions;
// The constructor should be universal across all environments. All environment specific behavior should be set by adding or overriding methods
constructor(config: Config) {
if (!config) throw new Error('ApolloServer requires options.');
@ -92,6 +90,7 @@ export class ApolloServerBase {
engine,
subscriptions,
uploads,
playground,
...requestOptions
} = config;
@ -115,14 +114,14 @@ export class ApolloServerBase {
: noIntro;
}
if (!requestOptions.cache) {
requestOptions.cache = new InMemoryLRUCache();
}
if (requestOptions.persistedQueries !== false) {
if (!requestOptions.persistedQueries) {
// maxSize is the number of elements that can be stored inside of the cache
// https://github.com/withspectrum/spectrum has about 200 instances of gql`
// 300 queries seems reasonable
const lru = new QuickLru({ maxSize: 300 });
requestOptions.persistedQueries = {
cache: new Keyv({ store: lru }) as PersistedQueryCache,
cache: requestOptions.cache!,
};
}
} else {
@ -130,10 +129,6 @@ export class ApolloServerBase {
delete requestOptions.persistedQueries;
}
if (!requestOptions.cache) {
requestOptions.cache = new InMemoryLRUCache();
}
this.requestOptions = requestOptions as GraphQLOptions;
this.context = context;
@ -244,6 +239,8 @@ export class ApolloServerBase {
);
}
}
this.playgroundOptions = createPlaygroundOptions(playground);
}
// used by integrations to synchronize the path with subscriptions, some

View file

@ -1,15 +1,6 @@
import { ExecutionResult } from 'graphql';
import { CacheControlFormat } from 'apollo-cache-control';
export interface PersistedQueryCache {
set(key: string, data: string): Promise<any>;
get(key: string): Promise<string | null>;
}
export type HttpHeaderCalculation = (
responses: Array<ExecutionResult & { extensions?: Record<string, any> }>,
) => Record<string, string>;
export function calculateCacheControlHeaders(
responses: Array<ExecutionResult & { extensions?: Record<string, any> }>,
): Record<string, string> {

View file

@ -3,10 +3,10 @@ import {
ValidationContext,
GraphQLFieldResolver,
} from 'graphql';
import { PersistedQueryCache, HttpHeaderCalculation } from './caching';
import { GraphQLExtension } from 'graphql-extensions';
import { CacheControlExtensionOptions } from 'apollo-cache-control';
import { KeyValueCache } from 'apollo-server-caching';
import { DataSource } from 'apollo-datasource';
/*
* GraphQLServerOptions
@ -15,7 +15,6 @@ import { KeyValueCache } from 'apollo-server-caching';
* - (optional) formatError: Formatting function applied to all errors before response is sent
* - (optional) rootValue: rootValue passed to GraphQL execution
* - (optional) context: the context passed to GraphQL execution
* - (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) fieldResolver: a custom default field resolver
@ -32,7 +31,6 @@ export interface GraphQLServerOptions<
formatError?: Function;
rootValue?: any;
context?: TContext;
formatParams?: Function;
validationRules?: Array<(context: ValidationContext) => any>;
formatResponse?: Function;
fieldResolver?: GraphQLFieldResolver<any, TContext>;
@ -41,7 +39,7 @@ export interface GraphQLServerOptions<
cacheControl?:
| boolean
| (CacheControlExtensionOptions & {
calculateHttpHeaders?: boolean | HttpHeaderCalculation;
calculateHttpHeaders?: boolean;
stripFormattedExtensions?: boolean;
});
extensions?: Array<() => GraphQLExtension>;
@ -50,16 +48,12 @@ export interface GraphQLServerOptions<
persistedQueries?: PersistedQueryOptions;
}
export interface DataSource<TContext> {
context: TContext;
}
export type DataSources<TContext> = {
[name: string]: DataSource<TContext>;
};
export interface PersistedQueryOptions {
cache: PersistedQueryCache;
cache: KeyValueCache;
}
export default GraphQLServerOptions;

View file

@ -22,6 +22,8 @@ export {
export { convertNodeHttpToRequest } from './nodeHttpToRequest';
export { createPlaygroundOptions } from './playground';
// ApolloServer Base class
export { ApolloServerBase } from './ApolloServer';
export * from './types';
@ -34,3 +36,7 @@ export const gql: (
template: TemplateStringsArray | string,
...substitutions: any[]
) => DocumentNode = gqlTag;
import { GraphQLScalarType } from 'graphql';
import { GraphQLUpload as UploadScalar } from 'apollo-upload-server';
export const GraphQLUpload = UploadScalar as GraphQLScalarType;

View file

@ -0,0 +1,52 @@
import {
RenderPageOptions as PlaygroundRenderPageOptions,
Theme,
} from '@apollographql/graphql-playground-html/dist/render-playground-page';
export {
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html/dist/render-playground-page';
// This specifies the version of GraphQL Playground that will be served
// from graphql-playground-html, and is passed to renderPlaygroundPage
// by the integration subclasses
const playgroundVersion = '1.7.2';
export type PlaygroundConfig = Partial<PlaygroundRenderPageOptions> | boolean;
export const defaultPlaygroundOptions = {
version: playgroundVersion,
settings: {
'general.betaUpdates': false,
'editor.theme': 'dark' as Theme,
'editor.reuseHeaders': true,
'tracing.hideTracingResponse': true,
'editor.fontSize': 14,
'editor.fontFamily': `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace`,
'request.credentials': 'omit',
},
};
export function createPlaygroundOptions(
playground: PlaygroundConfig = {},
): PlaygroundRenderPageOptions | undefined {
const isDev = process.env.NODE_ENV !== 'production';
const enabled: boolean = typeof playground === 'boolean' ? playground : isDev;
if (!enabled) {
return undefined;
}
const playgroundOverrides =
typeof playground === 'boolean' ? {} : playground || {};
const playgroundOptions: PlaygroundRenderPageOptions = {
...defaultPlaygroundOptions,
...playgroundOverrides,
settings: {
...defaultPlaygroundOptions.settings,
...playgroundOverrides.settings,
},
};
return playgroundOptions;
}

View file

@ -1,7 +1,6 @@
import { ExecutionResult } from 'graphql';
const sha256 = require('hash.js/lib/hash/sha/256');
import { HTTPCache } from 'apollo-datasource-rest';
import { CacheControlExtensionOptions } from 'apollo-cache-control';
import { omit } from 'lodash';
@ -17,7 +16,7 @@ import {
PersistedQueryNotSupportedError,
PersistedQueryNotFoundError,
} from 'apollo-server-errors';
import { calculateCacheControlHeaders, HttpHeaderCalculation } from './caching';
import { calculateCacheControlHeaders } from './caching';
export interface HttpQueryRequest {
method: string;
@ -104,7 +103,7 @@ export async function runHttpQuery(
process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
let cacheControl:
| CacheControlExtensionOptions & {
calculateHttpHeaders: boolean | HttpHeaderCalculation;
calculateHttpHeaders: boolean;
stripFormattedExtensions: boolean;
}
| undefined;
@ -217,7 +216,8 @@ export async function runHttpQuery(
if (queryString === undefined) {
queryString =
(await optionsObject.persistedQueries.cache.get(sha)) || undefined;
(await optionsObject.persistedQueries.cache.get(`apq:${sha}`)) ||
undefined;
if (queryString) {
persistedQueryHit = true;
} else {
@ -246,7 +246,10 @@ export async function runHttpQuery(
// We do not wait on the cache storage to complete
return (
optionsObject.persistedQueries &&
optionsObject.persistedQueries.cache.set(sha, queryString)
optionsObject.persistedQueries.cache.set(
`apq:${sha}`,
queryString,
)
);
})().catch(error => {
console.warn(error);
@ -254,8 +257,11 @@ export async function runHttpQuery(
}
}
// We ensure that there is a queryString or parsedQuery after formatParams
if (queryString && typeof queryString !== 'string') {
if (!queryString) {
throw new HttpQueryError(400, 'Must provide query string.');
}
if (typeof queryString !== 'string') {
// Check for a common error first.
if (queryString && (queryString as any).kind === 'Document') {
throw new HttpQueryError(
@ -334,12 +340,8 @@ export async function runHttpQuery(
if (optionsObject.dataSources) {
const dataSources = optionsObject.dataSources() || {};
// we use the cache provided to the request and add the Http semantics on top
const httpCache = new HTTPCache(optionsObject.cache);
for (const dataSource of Object.values(dataSources)) {
dataSource.context = context;
(dataSource as any).httpCache = httpCache;
dataSource.initialize(context, optionsObject.cache!);
}
if ('dataSources' in context) {
@ -402,15 +404,6 @@ export async function runHttpQuery(
persistedQueryRegister,
};
if (optionsObject.formatParams) {
params = optionsObject.formatParams(params);
}
if (!params.queryString && !params.parsedQuery) {
// Note that we've already thrown a different error if it looks like APQ.
throw new HttpQueryError(400, 'Must provide query string.');
}
return runQuery(params);
} catch (e) {
// Populate any HttpQueryError to our handler which should
@ -446,10 +439,7 @@ export async function runHttpQuery(
if (cacheControl) {
if (cacheControl.calculateHttpHeaders) {
const calculatedHeaders =
typeof cacheControl.calculateHttpHeaders === 'function'
? cacheControl.calculateHttpHeaders(responses)
: calculateCacheControlHeaders(responses);
const calculatedHeaders = calculateCacheControlHeaders(responses);
responseInit.headers = {
...responseInit.headers,

View file

@ -3,9 +3,13 @@ import { SchemaDirectiveVisitor, IResolvers, IMocks } from 'graphql-tools';
import { ConnectionContext } from 'subscriptions-transport-ws';
import * as WebSocket from 'ws';
import { GraphQLExtension } from 'graphql-extensions';
import { EngineReportingOptions } from 'apollo-engine-reporting';
export { GraphQLExtension } from 'graphql-extensions';
import { EngineReportingOptions } from 'apollo-engine-reporting';
import { PlaygroundConfig } from './playground';
export { PlaygroundConfig, PlaygroundRenderPageOptions } from './playground';
import {
GraphQLServerOptions as GraphQLOptions,
PersistedQueryOptions,
@ -37,7 +41,6 @@ export interface Config
| 'formatError'
| 'debug'
| 'rootValue'
| 'formatParams'
| 'validationRules'
| 'formatResponse'
| 'fieldResolver'
@ -59,6 +62,7 @@ export interface Config
subscriptions?: Partial<SubscriptionServerOptions> | string | false;
//https://github.com/jaydenseric/apollo-upload-server#options
uploads?: boolean | FileUploadOptions;
playground?: PlaygroundConfig;
}
export interface FileUploadOptions {
@ -69,9 +73,3 @@ export interface FileUploadOptions {
//Max allowed number of files (default: Infinity).
maxFiles?: number;
}
export interface MiddlewareOptions {
path?: string;
gui?: boolean;
subscriptions?: boolean;
}

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-env",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-errors",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-express",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Production-ready Node.js GraphQL server for Express and Connect",
"main": "dist/index.js",
"scripts": {
@ -35,7 +35,7 @@
"@types/cors": "^2.8.4",
"@types/express": "4.16.0",
"accepts": "^1.3.5",
"apollo-server-core": "^2.0.0-rc.6",
"apollo-server-core": "^2.0.0-rc.7",
"apollo-upload-server": "^5.0.0",
"body-parser": "^1.18.3",
"cors": "^2.8.4",
@ -46,8 +46,8 @@
"devDependencies": {
"@types/connect": "3.4.32",
"@types/multer": "1.3.6",
"apollo-datasource-rest": "^2.0.0-rc.6",
"apollo-server-integration-testsuite": "^2.0.0-rc.6",
"apollo-datasource-rest": "^2.0.0-rc.7",
"apollo-server-integration-testsuite": "^2.0.0-rc.7",
"connect": "3.6.6",
"express": "^4.16.3",
"form-data": "^2.3.2",

View file

@ -98,7 +98,7 @@ describe('apollo-server-express', () => {
// XXX Unclear why this would be something somebody would want (vs enabling
// introspection without graphql-playground, which seems reasonable, eg you
// have your own graphql-playground setup with a custom link)
it('can enable gui separately from introspection during production', async () => {
it('can enable playground separately from introspection during production', async () => {
const INTROSPECTION_QUERY = `
{
__schema {
@ -146,7 +146,7 @@ describe('apollo-server-express', () => {
});
});
it('renders GraphQL playground when browser requests', async () => {
it('renders GraphQL playground by default when browser requests', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;
@ -179,6 +179,101 @@ describe('apollo-server-express', () => {
});
});
it('accepts partial GraphQL Playground Options', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;
const defaultQuery = 'query { foo { bar } }';
const endpoint = '/fumanchupacabra';
const { url } = await createServer(
{
typeDefs,
resolvers,
playground: {
// https://github.com/apollographql/graphql-playground/blob/0e452d2005fcd26f10fbdcc4eed3b2e2af935e3a/packages/graphql-playground-html/src/render-playground-page.ts#L16-L24
// must be made partial
settings: {
'editor.theme': 'light',
} as any,
tabs: [
{
query: defaultQuery,
},
{
endpoint,
} as any,
],
},
},
{},
);
return new Promise<http.Server>((resolve, reject) => {
request(
{
url,
method: 'GET',
headers: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
Folo: 'bar',
},
},
(error, response, body) => {
process.env.NODE_ENV = nodeEnv;
if (error) {
reject(error);
} else {
console.log('body', body);
expect(body).to.contain('GraphQLPlayground');
expect(body).to.contain(`"editor.theme": "light"`);
expect(body).to.contain(defaultQuery);
expect(body).to.contain(endpoint);
expect(response.statusCode).to.equal(200);
resolve();
}
},
);
});
});
it('accepts playground options as a boolean', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;
const { url } = await createServer(
{
typeDefs,
resolvers,
playground: false,
},
{},
);
return new Promise<http.Server>((resolve, reject) => {
request(
{
url,
method: 'GET',
headers: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
},
},
(error, response, body) => {
process.env.NODE_ENV = nodeEnv;
if (error) {
reject(error);
} else {
expect(body).not.to.contain('GraphQLPlayground');
expect(response.statusCode).not.to.equal(200);
resolve();
}
},
);
});
});
it('accepts cors configuration', async () => {
const { url: uri } = await createServer(
{

View file

@ -5,7 +5,12 @@ import {
renderPlaygroundPage,
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html';
import { ApolloServerBase, formatApolloErrors } from 'apollo-server-core';
import {
GraphQLOptions,
FileUploadOptions,
ApolloServerBase,
formatApolloErrors,
} from 'apollo-server-core';
import * as accepts from 'accepts';
import * as typeis from 'type-is';
@ -14,7 +19,6 @@ import { graphqlExpress } from './expressApollo';
import { processRequest as processFileUploads } from 'apollo-upload-server';
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions, FileUploadOptions } from 'apollo-server-core';
export interface ServerRegistration {
// Note: You can also pass a connect.Server here. If we changed this field to
@ -29,7 +33,6 @@ export interface ServerRegistration {
bodyParserConfig?: OptionsJson | boolean;
onHealthCheck?: (req: express.Request) => Promise<any>;
disableHealthCheck?: boolean;
gui?: boolean;
}
const fileUploadMiddleware = (
@ -87,7 +90,6 @@ export class ApolloServer extends ApolloServerBase {
cors,
bodyParserConfig,
disableHealthCheck,
gui,
onHealthCheck,
}: ServerRegistration) {
if (!path) path = '/graphql';
@ -138,16 +140,15 @@ export class ApolloServer extends ApolloServerBase {
app.use(path, uploadsMiddleware);
}
// Note: if you enable a gui in production and expect to be able to see your
// Note: if you enable playground in production and expect to be able to see your
// schema, you'll need to manually specify `introspection: true` in the
// ApolloServer constructor; by default, the introspection query is only
// enabled in dev.
const guiEnabled =
!!gui || (gui === undefined && process.env.NODE_ENV !== 'production');
app.use(path, (req, res, next) => {
if (guiEnabled && req.method === 'GET') {
if (this.playgroundOptions && req.method === 'GET') {
// perform more expensive content-type check only if necessary
// XXX We could potentially move this logic into the GuiOptions lambda,
// but I don't think it needs any overriding
const accept = accepts(req);
const types = accept.types() as string[];
const prefersHTML =
@ -159,7 +160,7 @@ export class ApolloServer extends ApolloServerBase {
const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {
endpoint: path,
subscriptionEndpoint: this.subscriptionsPath,
version: this.playgroundVersion,
...this.playgroundOptions,
};
res.setHeader('Content-Type', 'text/html');
const playground = renderPlaygroundPage(playgroundRenderPageOptions);

View file

@ -1,6 +1,8 @@
export { GraphQLOptions, gql } from 'apollo-server-core';
export {
GraphQLUpload,
GraphQLOptions,
gql,
// Errors
ApolloError,
toApolloError,
SyntaxError,
@ -19,3 +21,6 @@ export {
registerServer,
ServerRegistration,
} from './ApolloServer';
export { CorsOptions } from 'cors';
export { OptionsJson } from 'body-parser';

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-hapi",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Production-ready Node.js GraphQL server for Hapi",
"main": "dist/index.js",
"scripts": {
@ -30,7 +30,7 @@
"dependencies": {
"@apollographql/graphql-playground-html": "^1.6.0",
"accept": "^3.0.2",
"apollo-server-core": "^2.0.0-rc.6",
"apollo-server-core": "^2.0.0-rc.7",
"apollo-upload-server": "^5.0.0",
"boom": "^7.1.0",
"graphql-subscriptions": "^0.5.8",
@ -38,7 +38,7 @@
},
"devDependencies": {
"@types/hapi": "^17.0.12",
"apollo-server-integration-testsuite": "^2.0.0-rc.6",
"apollo-server-integration-testsuite": "^2.0.0-rc.7",
"hapi": "17.4.0"
},
"peerDependencies": {

View file

@ -87,7 +87,7 @@ describe('apollo-server-hapi', () => {
// XXX Unclear why this would be something somebody would want (vs enabling
// introspection without graphql-playground, which seems reasonable, eg you
// have your own graphql-playground setup with a custom link)
it('can enable gui separately from introspection during production', async () => {
it('can enable playground separately from introspection during production', async () => {
const INTROSPECTION_QUERY = `
{
__schema {

View file

@ -1,5 +1,4 @@
import * as hapi from 'hapi';
import { ApolloServerBase } from 'apollo-server-core';
import { parseAll } from 'accept';
import {
renderPlaygroundPage,
@ -10,7 +9,11 @@ import { processRequest as processFileUploads } from 'apollo-upload-server';
import { graphqlHapi } from './hapiApollo';
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions, FileUploadOptions } from 'apollo-server-core';
import {
ApolloServerBase,
GraphQLOptions,
FileUploadOptions,
} from 'apollo-server-core';
function handleFileUploads(uploadsConfig: FileUploadOptions) {
return async (request: hapi.Request) => {
@ -47,7 +50,6 @@ export class ApolloServer extends ApolloServerBase {
cors,
path,
disableHealthCheck,
gui,
onHealthCheck,
}: ServerRegistration) {
if (!path) path = '/graphql';
@ -63,15 +65,7 @@ export class ApolloServer extends ApolloServerBase {
await handleFileUploads(this.uploadsConfig)(request);
}
// Note: if you enable a gui in production and expect to be able to see your
// schema, you'll need to manually specify `introspection: true` in the
// ApolloServer constructor; by default, the introspection query is only
// enabled in dev.
const guiEnabled =
!!gui || (gui === undefined && process.env.NODE_ENV !== 'production');
// enableGUI takes precedence over the server tools setting
if (guiEnabled && request.method === 'get') {
if (this.playgroundOptions && request.method === 'get') {
// perform more expensive content-type check only if necessary
const accept = parseAll(request.headers);
const types = accept.mediaTypes as string[];
@ -85,6 +79,7 @@ export class ApolloServer extends ApolloServerBase {
endpoint: path,
subscriptionEndpoint: this.subscriptionsPath,
version: this.playgroundVersion,
...this.playgroundOptions,
};
return h
@ -143,7 +138,6 @@ export interface ServerRegistration {
cors?: boolean | hapi.RouteOptionsCors;
onHealthCheck?: (request: hapi.Request) => Promise<any>;
disableHealthCheck?: boolean;
gui?: boolean;
uploads?: boolean | Record<string, any>;
}

View file

@ -1,6 +1,8 @@
export { GraphQLOptions, gql } from 'apollo-server-core';
export {
GraphQLUpload,
GraphQLOptions,
gql,
// Errors
ApolloError,
toApolloError,
SyntaxError,

View file

@ -1,7 +1,7 @@
{
"name": "apollo-server-integration-testsuite",
"private": true,
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Apollo Server Integrations testsuite",
"main": "dist/index.js",
"scripts": {
@ -23,7 +23,7 @@
"node": ">=6"
},
"dependencies": {
"apollo-server-core": "^2.0.0-rc.6"
"apollo-server-core": "^2.0.0-rc.7"
},
"devDependencies": {
"@types/body-parser": "1.17.0",
@ -32,9 +32,9 @@
"apollo-link": "^1.2.2",
"apollo-link-http": "^1.5.4",
"apollo-link-persisted-queries": "^0.2.1",
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.7",
"body-parser": "^1.18.3",
"graphql-extensions": "^0.1.0-rc.0",
"graphql-extensions": "^0.1.0-rc.1",
"graphql-subscriptions": "^0.5.8",
"graphql-tag": "^2.9.2",
"js-sha256": "^0.9.0",

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-koa",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Production-ready Node.js GraphQL server for Koa",
"main": "dist/index.js",
"scripts": {
@ -29,15 +29,15 @@
},
"dependencies": {
"@apollographql/graphql-playground-html": "^1.6.0",
"@koa/cors": "^2.2.1",
"@types/accepts": "^1.3.5",
"@types/cors": "^2.8.4",
"@types/koa": "^2.0.46",
"@types/koa-bodyparser": "^4.2.0",
"@types/koa-bodyparser": "^5.0.1",
"@types/koa-compose": "^3.2.2",
"@types/koa__cors": "^2.2.1",
"@koa/cors": "^2.2.1",
"accepts": "^1.3.5",
"apollo-server-core": "^2.0.0-rc.6",
"apollo-server-core": "^2.0.0-rc.7",
"apollo-upload-server": "^5.0.0",
"graphql-subscriptions": "^0.5.8",
"graphql-tools": "^3.0.4",
@ -49,8 +49,8 @@
"devDependencies": {
"@types/koa-multer": "^1.0.0",
"@types/koa-router": "^7.0.30",
"apollo-datasource-rest": "^2.0.0-rc.6",
"apollo-server-integration-testsuite": "^2.0.0-rc.6",
"apollo-datasource-rest": "^2.0.0-rc.7",
"apollo-server-integration-testsuite": "^2.0.0-rc.7",
"form-data": "^2.3.2",
"koa-multer": "^1.0.2",
"node-fetch": "^2.1.2"

View file

@ -98,7 +98,7 @@ describe('apollo-server-koa', () => {
// XXX Unclear why this would be something somebody would want (vs enabling
// introspection without graphql-playground, which seems reasonable, eg you
// have your own graphql-playground setup with a custom link)
it('can enable gui separately from introspection during production', async () => {
it('can enable playground separately from introspection during production', async () => {
const INTROSPECTION_QUERY = `
{
__schema {

View file

@ -17,20 +17,13 @@ import { processRequest as processFileUploads } from 'apollo-upload-server';
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions, FileUploadOptions } from 'apollo-server-core';
// koa-bodyparser does not expose an Options interface so we infer the type here.
// we can replace this when this PR get merged: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/27047
export type BodyParserOptions = typeof bodyParser extends (opts: infer U) => any
? U
: never;
export interface ServerRegistration {
app: Koa;
path?: string;
cors?: corsMiddleware.Options | boolean;
bodyParserConfig?: BodyParserOptions | boolean;
bodyParserConfig?: bodyParser.Options | boolean;
onHealthCheck?: (ctx: Koa.Context) => Promise<any>;
disableHealthCheck?: boolean;
gui?: boolean;
}
const fileUploadMiddleware = (
@ -87,7 +80,6 @@ export class ApolloServer extends ApolloServerBase {
cors,
bodyParserConfig,
disableHealthCheck,
gui,
onHealthCheck,
}: ServerRegistration) {
if (!path) path = '/graphql';
@ -141,16 +133,9 @@ export class ApolloServer extends ApolloServerBase {
app.use(middlewareFromPath(path, uploadsMiddleware));
}
// Note: if you enable a gui in production and expect to be able to see your
// schema, you'll need to manually specify `introspection: true` in the
// ApolloServer constructor; by default, the introspection query is only
// enabled in dev.
const guiEnabled =
!!gui || (gui === undefined && process.env.NODE_ENV !== 'production');
app.use(
middlewareFromPath(path, (ctx: Koa.Context, next: Function) => {
if (guiEnabled && ctx.request.method === 'GET') {
if (this.playgroundOptions && ctx.request.method === 'GET') {
// perform more expensive content-type check only if necessary
const accept = accepts(ctx.req);
const types = accept.types() as string[];
@ -163,7 +148,7 @@ export class ApolloServer extends ApolloServerBase {
const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {
endpoint: path,
subscriptionEndpoint: this.subscriptionsPath,
version: this.playgroundVersion,
...this.playgroundOptions,
};
ctx.set('Content-Type', 'text/html');
const playground = renderPlaygroundPage(

View file

@ -1,6 +1,8 @@
export { GraphQLOptions, gql } from 'apollo-server-core';
export {
GraphQLUpload,
GraphQLOptions,
gql,
// Errors
ApolloError,
toApolloError,
SyntaxError,

View file

@ -17,6 +17,8 @@ To deploy the AWS Lambda function we must create a Cloudformation Template and a
#### 1. Write the API handlers
In a file named `graphql.js`, place the following code:
```js
const { ApolloServer, gql } = require('apollo-server-lambda');
@ -136,7 +138,7 @@ exports.graphqlHandler = server.createHandler();
## Modifying the Lambda Response (Enable CORS)
To enable CORS the response HTTP headers need to be modified. To accomplish this use `cors` options.
To enable CORS the response HTTP headers need to be modified. To accomplish this use the `cors` option.
```js
const { ApolloServer, gql } = require('apollo-server-lambda');

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-lambda",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Production-ready Node.js GraphQL server for AWS Lambda",
"keywords": [
"GraphQL",
@ -31,12 +31,13 @@
},
"dependencies": {
"@apollographql/graphql-playground-html": "^1.6.0",
"apollo-server-core": "^2.0.0-rc.6",
"apollo-server-core": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7",
"graphql-tools": "^3.0.4"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.6",
"apollo-server-integration-testsuite": "^2.0.0-rc.6"
"apollo-server-integration-testsuite": "^2.0.0-rc.7"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"

View file

@ -1,7 +1,7 @@
import * as lambda from 'aws-lambda';
import { ApolloServerBase } from 'apollo-server-core';
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions } from 'apollo-server-core';
import { GraphQLOptions, Config } from 'apollo-server-core';
import {
renderPlaygroundPage,
RenderPageOptions as PlaygroundRenderPageOptions,
@ -10,7 +10,6 @@ import {
import { graphqlLambda } from './lambdaApollo';
export interface CreateHandlerOptions {
gui?: boolean;
cors?: {
origin?: boolean | string | string[];
methods?: string | string[];
@ -22,6 +21,19 @@ export interface CreateHandlerOptions {
}
export class ApolloServer extends ApolloServerBase {
// If you feel tempted to add an option to this constructor. Please consider
// another place, since the documentation becomes much more complicated when
// the constructor is not longer shared between all integration
constructor(options: Config) {
if (process.env.ENGINE_API_KEY || options.engine) {
options.engine = {
sendReportsImmediately: true,
...(typeof options.engine !== 'boolean' ? options.engine : {}),
};
}
super(options);
}
// This translates the arguments from the middleware into graphQL options It
// provides typings for the integration specific behavior, ideally this would
// be propagated with a generic to the super class
@ -32,13 +44,7 @@ export class ApolloServer extends ApolloServerBase {
return super.graphQLServerOptions({ event, context });
}
// Added "= { gui: undefined }" to fix "module initialization error: TypeError"
public createHandler(
{ gui, cors }: CreateHandlerOptions = { gui: undefined, cors: undefined },
) {
const guiEnabled =
!!gui || (gui === undefined && process.env.NODE_ENV !== 'production');
public createHandler({ cors }: CreateHandlerOptions = { cors: undefined }) {
const corsHeaders = {};
if (cors) {
@ -98,12 +104,12 @@ export class ApolloServer extends ApolloServerBase {
}
}
if (guiEnabled && event.httpMethod === 'GET') {
if (this.playgroundOptions && event.httpMethod === 'GET') {
const acceptHeader = event.headers['Accept'] || event.headers['accept'];
if (acceptHeader && acceptHeader.includes('text/html')) {
const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {
endpoint: event.requestContext.path,
version: this.playgroundVersion,
...this.playgroundOptions,
};
return callback(null, {

View file

@ -1,6 +1,8 @@
export { GraphQLOptions, gql } from 'apollo-server-core';
export {
GraphQLUpload,
GraphQLOptions,
gql,
// Errors
ApolloError,
toApolloError,
SyntaxError,

View file

@ -4,6 +4,7 @@ import {
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import { Headers } from 'apollo-server-env';
export interface LambdaGraphQLOptionsFunction {
(event: lambda.APIGatewayProxyEvent, context: lambda.Context):
@ -45,7 +46,7 @@ export function graphqlLambda(
request: {
url: event.path,
method: event.httpMethod,
headers: event.headers as any,
headers: new Headers(event.headers),
},
}).then(
({ graphqlResponse, responseInit }) => {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-memcached",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
@ -23,8 +23,8 @@
"node": ">=6"
},
"dependencies": {
"apollo-server-caching": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-caching": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7",
"memcached": "^2.2.2"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-micro",
"version": "2.0.0-rc.5",
"version": "2.0.0-rc.7",
"description": "Production-ready Node.js GraphQL server for Micro",
"main": "dist/index.js",
"scripts": {
@ -28,14 +28,14 @@
"homepage": "https://github.com/apollographql/apollo-server#readme",
"dependencies": {
"accept": "^3.0.2",
"apollo-server-core": "^2.0.0-rc.5",
"apollo-server-core": "^2.0.0-rc.7",
"apollo-upload-server": "^5.0.0",
"graphql-playground-html": "^1.6.0",
"micro": "^9.3.2"
},
"devDependencies": {
"@types/micro": "^7.3.1",
"apollo-server-integration-testsuite": "^2.0.0-rc.5",
"apollo-server-integration-testsuite": "^2.0.0-rc.7",
"request-promise": "^4.2.2",
"test-listen": "^1.1.0"
},

View file

@ -2,10 +2,7 @@ import { ApolloServerBase, GraphQLOptions } from 'apollo-server-core';
import { processRequest as processFileUploads } from 'apollo-upload-server';
import { ServerResponse } from 'http';
import { send } from 'micro';
import {
renderPlaygroundPage,
MiddlewareOptions as PlaygroundMiddlewareOptions,
} from 'graphql-playground-html';
import { renderPlaygroundPage } from 'graphql-playground-html';
import { parseAll } from 'accept';
import { graphqlMicro } from './microApollo';
@ -15,7 +12,6 @@ export interface ServerRegistration {
path?: string;
disableHealthCheck?: boolean;
onHealthCheck?: (req: MicroRequest) => Promise<any>;
gui?: boolean | PlaygroundMiddlewareOptions;
}
export class ApolloServer extends ApolloServerBase {
@ -33,7 +29,6 @@ export class ApolloServer extends ApolloServerBase {
path,
disableHealthCheck,
onHealthCheck,
gui,
}: ServerRegistration = {}) {
return async (req, res) => {
this.graphqlPath = path || '/graphql';
@ -46,7 +41,7 @@ export class ApolloServer extends ApolloServerBase {
disableHealthCheck,
onHealthCheck,
})) ||
this.handleGraphqlRequestsWithPlayground({ req, res, gui }) ||
this.handleGraphqlRequestsWithPlayground({ req, res }) ||
(await this.handleGraphqlRequestsWithServer({ req, res })) ||
send(res, 404, null);
};
@ -103,23 +98,19 @@ export class ApolloServer extends ApolloServerBase {
return handled;
}
// If the `gui` option is set, register a `graphql-playground` instance
// If the `playgroundOptions` are set, register a `graphql-playground` instance
// (not available in production) that is then used to handle all
// incoming GraphQL requests.
private handleGraphqlRequestsWithPlayground({
req,
res,
gui,
}: {
req: MicroRequest;
res: ServerResponse;
gui?: boolean | PlaygroundMiddlewareOptions;
}): boolean {
let handled = false;
const guiEnabled =
!!gui || (gui === undefined && process.env.NODE_ENV !== 'production');
if (guiEnabled && req.method === 'GET') {
if (this.playgroundOptions && req.method === 'GET') {
const accept = parseAll(req.headers);
const types = accept.mediaTypes as string[];
const prefersHTML =
@ -131,8 +122,7 @@ export class ApolloServer extends ApolloServerBase {
const middlewareOptions = {
endpoint: this.graphqlPath,
subscriptionEndpoint: this.subscriptionsPath,
version: '1.7.0',
...(typeof gui === 'boolean' ? {} : gui),
...this.playgroundOptions,
};
send(res, 200, renderPlaygroundPage(middlewareOptions));
handled = true;

View file

@ -1,4 +1,8 @@
export {
GraphQLUpload,
GraphQLOptions,
gql,
// Errors
ApolloError,
toApolloError,
SyntaxError,
@ -6,8 +10,6 @@ export {
AuthenticationError,
ForbiddenError,
UserInputError,
GraphQLOptions,
gql,
} from 'apollo-server-core';
export * from 'graphql-tools';

View file

@ -13,7 +13,7 @@ function createApp(options: CreateAppOptions = {}) {
const server = new ApolloServer(
(options.graphqlOptions as Config) || { schema: Schema },
);
return micro(server.createHandler({ gui: false }));
return micro(server.createHandler());
}
describe('microApollo', function() {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server-redis",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
@ -23,8 +23,8 @@
"node": ">=6"
},
"dependencies": {
"apollo-server-caching": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-caching": "^2.0.0-rc.7",
"apollo-server-env": "^2.0.0-rc.7",
"redis": "^2.8.0"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "apollo-server",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.7",
"description": "Production ready GraphQL Server",
"author": "opensource@apollographql.com",
"main": "dist/index.js",
@ -25,8 +25,8 @@
},
"homepage": "https://github.com/apollographql/apollo-server#readme",
"dependencies": {
"apollo-server-core": "^2.0.0-rc.6",
"apollo-server-express": "^2.0.0-rc.6",
"apollo-server-core": "^2.0.0-rc.7",
"apollo-server-express": "^2.0.0-rc.7",
"express": "^4.0.0",
"graphql-subscriptions": "^0.5.8",
"graphql-tools": "^3.0.4"

View file

@ -102,6 +102,26 @@ describe('apollo-server', () => {
await apolloFetch({ query: '{hello}' });
});
it('configures cors', async () => {
server = new ApolloServer({
typeDefs,
resolvers,
cors: { origin: 'localhost' },
});
const { url: uri } = await server.listen();
const apolloFetch = createApolloFetch({ uri }).useAfter(
(response, next) => {
expect(
response.response.headers.get('access-control-allow-origin'),
).to.equal('localhost');
next();
},
);
await apolloFetch({ query: '{hello}' });
});
it('creates a healthcheck endpoint', async () => {
server = new ApolloServer({
typeDefs,

View file

@ -5,9 +5,21 @@
import * as express from 'express';
import * as http from 'http';
import * as net from 'net';
import { ApolloServer as ApolloServerBase } from 'apollo-server-express';
import {
ApolloServer as ApolloServerBase,
CorsOptions,
} from 'apollo-server-express';
import { Config } from 'apollo-server-core';
export { GraphQLOptions, GraphQLExtension, gql } from 'apollo-server-core';
export {
GraphQLUpload,
GraphQLOptions,
GraphQLExtension,
gql,
Config,
} from 'apollo-server-core';
export { CorsOptions } from 'apollo-server-express';
export * from './exports';
@ -22,7 +34,13 @@ export interface ServerInfo {
}
export class ApolloServer extends ApolloServerBase {
private httpServer: http.Server;
private httpServer!: http.Server;
private cors?: CorsOptions | boolean;
constructor(config: Config & { cors?: CorsOptions | boolean }) {
super(config);
this.cors = config && config.cors;
}
private createServerInfo(
server: http.Server,
@ -73,9 +91,12 @@ export class ApolloServer extends ApolloServerBase {
app,
path: '/',
bodyParserConfig: { limit: '50mb' },
cors: {
origin: '*',
},
cors:
typeof this.cors !== 'undefined'
? this.cors
: {
origin: '*',
},
});
this.httpServer = http.createServer(app);

View file

@ -1,6 +1,6 @@
{
"name": "apollo-tracing",
"version": "0.2.0-beta.1",
"version": "0.2.0-rc.0",
"description": "Collect and expose trace data for GraphQL requests",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@ -18,7 +18,7 @@
"node": ">=4.0"
},
"dependencies": {
"apollo-server-env": "^2.0.0-rc.6",
"apollo-server-env": "^2.0.0-rc.7",
"graphql-extensions": "0.1.0-beta.0"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "graphql-extensions",
"version": "0.1.0-rc.0",
"version": "0.1.0-rc.1",
"description": "Add extensions to GraphQL servers",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@ -22,7 +22,7 @@
"node": ">=6.0"
},
"dependencies": {
"apollo-server-env": "^2.0.0-rc.6"
"apollo-server-env": "^2.0.0-rc.7"
},
"peerDependencies": {
"graphql": "0.10.x - 0.13.x"