mirror of
https://github.com/vale981/apollo-server
synced 2025-03-05 09:41:40 -05:00
DeleteD
This commit is contained in:
commit
a31501ff66
63 changed files with 871 additions and 359 deletions
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
53
docs/source/features/playground.md
Normal file
53
docs/source/features/playground.md
Normal 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">
|
||||

|
||||
</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}`);
|
||||
});
|
||||
```
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
||||
|
|
|
@ -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`);
|
||||
});
|
||||
```
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export { RESTDataSource, RequestOptions } from './RESTDataSource';
|
||||
export { HTTPCache } from './HTTPCache';
|
||||
export { KeyValueCache, InMemoryLRUCache } from 'apollo-server-caching';
|
||||
|
|
6
packages/apollo-datasource/.npmignore
Normal file
6
packages/apollo-datasource/.npmignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
*
|
||||
!src/**/*
|
||||
!dist/**/*
|
||||
dist/**/*.test.*
|
||||
!package.json
|
||||
!README.md
|
57
packages/apollo-datasource/package.json
Normal file
57
packages/apollo-datasource/package.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
packages/apollo-datasource/src/index.ts
Normal file
5
packages/apollo-datasource/src/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { KeyValueCache } from 'apollo-server-caching';
|
||||
|
||||
export abstract class DataSource<TContext = any> {
|
||||
abstract initialize(context: TContext, cache: KeyValueCache): void;
|
||||
}
|
17
packages/apollo-datasource/tsconfig.json
Normal file
17
packages/apollo-datasource/tsconfig.json
Normal 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__/*"]
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
52
packages/apollo-server-core/src/playground.ts
Normal file
52
packages/apollo-server-core/src/playground.ts
Normal 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;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export { GraphQLOptions, gql } from 'apollo-server-core';
|
||||
|
||||
export {
|
||||
GraphQLUpload,
|
||||
GraphQLOptions,
|
||||
gql,
|
||||
// Errors
|
||||
ApolloError,
|
||||
toApolloError,
|
||||
SyntaxError,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export { GraphQLOptions, gql } from 'apollo-server-core';
|
||||
|
||||
export {
|
||||
GraphQLUpload,
|
||||
GraphQLOptions,
|
||||
gql,
|
||||
// Errors
|
||||
ApolloError,
|
||||
toApolloError,
|
||||
SyntaxError,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export { GraphQLOptions, gql } from 'apollo-server-core';
|
||||
|
||||
export {
|
||||
GraphQLUpload,
|
||||
GraphQLOptions,
|
||||
gql,
|
||||
// Errors
|
||||
ApolloError,
|
||||
toApolloError,
|
||||
SyntaxError,
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue