mirror of
https://github.com/vale981/apollo-server
synced 2025-03-04 17:21:42 -05:00
Merge branch 'master' into release-vNEXT
This commit is contained in:
commit
ccd974dbc6
38 changed files with 386 additions and 291 deletions
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
### v2.3.1
|
||||
|
||||
- Provide types for `graphql-upload` in a location where they can be accessed by TypeScript consumers of `apollo-server` packages. [ccf935f9](https://github.com/apollographql/apollo-server/commit/ccf935f9) [Issue #2092](https://github.com/apollographql/apollo-server/issues/2092)
|
||||
|
||||
### v2.3.0
|
||||
|
||||
- **BREAKING FOR NODE.JS <= 8.5.0 ONLY**: To continue using Apollo Server 2.x in versions of Node.js prior to v8.5.0, file uploads must be disabled by setting `uploads: false` on the `ApolloServer` constructor options. Without explicitly disabling file-uploads, the server will `throw` at launch (with instructions and a link to our documentation).
|
||||
|
@ -10,6 +14,8 @@
|
|||
|
||||
**We intend to drop support for Node.js 6.x in the next major version of Apollo Server.**
|
||||
|
||||
For more information, see [PR #2054](https://github.com/apollographql/apollo-server/pull/2054) and [our documentation](https://www.apollographql.com/docs/apollo-server/v2/migration-file-uploads.html).
|
||||
|
||||
### v2.2.7
|
||||
|
||||
- `apollo-engine-reporting`: When multiple instances of `apollo-engine-reporting` are loaded (an uncommon edge case), ensure that `encodedTraces` are handled only once rather than once per loaded instance. [PR #2040](https://github.com/apollographql/apollo-server/pull/2040)
|
||||
|
|
|
@ -16,18 +16,18 @@ const { gql } = require('apollo-server');
|
|||
const { find, filter } = require('lodash');
|
||||
|
||||
const schema = gql`
|
||||
type Book {
|
||||
title: String
|
||||
author: Author
|
||||
}
|
||||
type Book {
|
||||
title: String
|
||||
author: Author
|
||||
}
|
||||
|
||||
type Author {
|
||||
books: [Book]
|
||||
}
|
||||
type Author {
|
||||
books: [Book]
|
||||
}
|
||||
|
||||
type Query {
|
||||
author: Author
|
||||
}
|
||||
type Query {
|
||||
author: Author
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
|
|
@ -53,10 +53,11 @@ In order to apply this implementation to a schema that contains `@deprecated` di
|
|||
const { ApolloServer, gql } = require("apollo-server");
|
||||
|
||||
const typeDefs = gql`
|
||||
type ExampleType {
|
||||
newField: String
|
||||
oldField: String @deprecated(reason: "Use \`newField\`.")
|
||||
}`;
|
||||
type ExampleType {
|
||||
newField: String
|
||||
oldField: String @deprecated(reason: "Use \`newField\`.")
|
||||
}
|
||||
`;
|
||||
|
||||
const server = new ApolloServer({
|
||||
typeDefs,
|
||||
|
@ -96,11 +97,12 @@ const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server");
|
|||
const { defaultFieldResolver } = require("graphql");
|
||||
|
||||
const typeDefs = gql`
|
||||
directive @upper on FIELD_DEFINITION
|
||||
directive @upper on FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
hello: String @upper
|
||||
}`;
|
||||
type Query {
|
||||
hello: String @upper
|
||||
}
|
||||
`;
|
||||
|
||||
class UpperCaseDirective extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field) {
|
||||
|
@ -138,11 +140,12 @@ Suppose you've defined an object type that corresponds to a [REST](https://en.wi
|
|||
const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server");
|
||||
|
||||
const typeDefs = gql`
|
||||
directive @rest(url: String) on FIELD_DEFINITION
|
||||
directive @rest(url: String) on FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
people: [Person] @rest(url: "/api/v1/people")
|
||||
}`;
|
||||
type Query {
|
||||
people: [Person] @rest(url: "/api/v1/people")
|
||||
}
|
||||
`;
|
||||
|
||||
class RestDirective extends SchemaDirectiveVisitor {
|
||||
public visitFieldDefinition(field) {
|
||||
|
@ -173,13 +176,14 @@ Suppose your resolver returns a `Date` object but you want to return a formatted
|
|||
const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server");
|
||||
|
||||
const typeDefs = gql`
|
||||
directive @date(format: String) on FIELD_DEFINITION
|
||||
directive @date(format: String) on FIELD_DEFINITION
|
||||
|
||||
scalar Date
|
||||
scalar Date
|
||||
|
||||
type Post {
|
||||
published: Date @date(format: "mmmm d, yyyy")
|
||||
}`;
|
||||
type Post {
|
||||
published: Date @date(format: "mmmm d, yyyy")
|
||||
}
|
||||
`;
|
||||
|
||||
class DateFormatDirective extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field) {
|
||||
|
@ -214,15 +218,16 @@ const formatDate = require("dateformat");
|
|||
const { defaultFieldResolver, GraphQLString } = require("graphql");
|
||||
|
||||
const typeDefs = gql`
|
||||
directive @date(
|
||||
defaultFormat: String = "mmmm d, yyyy"
|
||||
) on FIELD_DEFINITION
|
||||
directive @date(
|
||||
defaultFormat: String = "mmmm d, yyyy"
|
||||
) on FIELD_DEFINITION
|
||||
|
||||
scalar Date
|
||||
scalar Date
|
||||
|
||||
type Query {
|
||||
today: Date @date
|
||||
}`;
|
||||
type Query {
|
||||
today: Date @date
|
||||
}
|
||||
`;
|
||||
|
||||
class FormattableDateDirective extends SchemaDirectiveVisitor {
|
||||
public visitFieldDefinition(field) {
|
||||
|
@ -292,11 +297,12 @@ Here's how you might make sure `translate` is used to localize the `greeting` fi
|
|||
const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server");
|
||||
|
||||
const typeDefs = gql`
|
||||
directive @intl on FIELD_DEFINITION
|
||||
directive @intl on FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
greeting: String @intl
|
||||
}`;
|
||||
type Query {
|
||||
greeting: String @intl
|
||||
}
|
||||
`;
|
||||
|
||||
class IntlDirective extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field, details) {
|
||||
|
@ -519,27 +525,28 @@ const { GraphQLID } = require("graphql");
|
|||
const { createHash } = require("crypto");
|
||||
|
||||
const typeDefs = gql`
|
||||
directive @uniqueID(
|
||||
# The name of the new ID field, "uid" by default:
|
||||
name: String = "uid"
|
||||
directive @uniqueID(
|
||||
# The name of the new ID field, "uid" by default:
|
||||
name: String = "uid"
|
||||
|
||||
# Which fields to include in the new ID:
|
||||
from: [String] = ["id"]
|
||||
) on OBJECT
|
||||
# Which fields to include in the new ID:
|
||||
from: [String] = ["id"]
|
||||
) on OBJECT
|
||||
|
||||
# Since this type just uses the default values of name and from,
|
||||
# we don't have to pass any arguments to the directive:
|
||||
type Location @uniqueID {
|
||||
id: Int
|
||||
address: String
|
||||
}
|
||||
# Since this type just uses the default values of name and from,
|
||||
# we don't have to pass any arguments to the directive:
|
||||
type Location @uniqueID {
|
||||
id: Int
|
||||
address: String
|
||||
}
|
||||
|
||||
# This type uses both the person's name and the personID field,
|
||||
# in addition to the "Person" type name, to construct the ID:
|
||||
type Person @uniqueID(from: ["name", "personID"]) {
|
||||
personID: Int
|
||||
name: String
|
||||
}`;
|
||||
# This type uses both the person's name and the personID field,
|
||||
# in addition to the "Person" type name, to construct the ID:
|
||||
type Person @uniqueID(from: ["name", "personID"]) {
|
||||
personID: Int
|
||||
name: String
|
||||
}
|
||||
`;
|
||||
|
||||
class UniqueIdDirective extends SchemaDirectiveVisitor {
|
||||
visitObject(type) {
|
||||
|
|
|
@ -15,9 +15,9 @@ This example demonstrates mocking a GraphQL schema with just one line of code, u
|
|||
const { ApolloServer, gql } = require('apollo-server');
|
||||
|
||||
const typeDefs = gql`
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`;
|
||||
|
||||
const server = new ApolloServer({
|
||||
|
@ -32,7 +32,7 @@ server.listen().then(({ url }) => {
|
|||
|
||||
> Note: If `typeDefs` has custom scalar types, `resolvers` must still contain the `serialize`, `parseValue`, and `parseLiteral` functions
|
||||
|
||||
Mocking logic simply looks at the type definitions and returns a string where a string is expected, a number for a number, etc. This provides the right shape of result. By default, when using mocks, any existing resolvers are ignored. See the ["Using existing resolvers with mocks"](#existing-resolvers) section below for more info on how to change this behavior.
|
||||
Mocking logic simply looks at the type definitions and returns a string where a string is expected, a number for a number, etc. This provides the right shape of result. By default, when using mocks, any existing resolvers are ignored. See the ["Using existing resolvers with mocks"](#existing-resolvers) section below for more info on how to change this behavior.
|
||||
|
||||
For more sophisticated testing, mocks can be customized to a particular data model.
|
||||
|
||||
|
@ -44,10 +44,10 @@ In addition to a boolean, `mocks` can be an object that describes custom mocking
|
|||
const { ApolloServer, gql } = require('apollo-server');
|
||||
|
||||
const typeDefs = gql`
|
||||
type Query {
|
||||
hello: String
|
||||
resolved: String
|
||||
}
|
||||
type Query {
|
||||
hello: String
|
||||
resolved: String
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
@ -149,16 +149,16 @@ For some more background and flavor on this approach, read the ["Mocking your se
|
|||
|
||||
The default behavior for mocks is to overwrite the resolvers already present in the schema. To keep the existing resolvers, set the `mockEntireSchema` option to false.
|
||||
|
||||
> Note: mocking resolvers will not work if the `mocks` option is `false`, even if `mockEntireSchema` is true.
|
||||
> Note: mocking resolvers will not work if the `mocks` option is `false`, even if `mockEntireSchema` is true.
|
||||
|
||||
```js line=26
|
||||
const { ApolloServer, gql } = require('apollo-server');
|
||||
|
||||
const typeDefs = gql`
|
||||
type Query {
|
||||
hello: String
|
||||
resolved: String
|
||||
}
|
||||
type Query {
|
||||
hello: String
|
||||
resolved: String
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
|
|
@ -36,17 +36,15 @@ const { ApolloServer, gql } = require('apollo-server');
|
|||
const GraphQLJSON = require('graphql-type-json');
|
||||
|
||||
const schemaString = gql`
|
||||
scalar JSON
|
||||
|
||||
scalar JSON
|
||||
|
||||
type Foo {
|
||||
aField: JSON
|
||||
}
|
||||
|
||||
type Query {
|
||||
foo: Foo
|
||||
}
|
||||
type Foo {
|
||||
aField: JSON
|
||||
}
|
||||
|
||||
type Query {
|
||||
foo: Foo
|
||||
}
|
||||
`;
|
||||
|
||||
const resolveFunctions = {
|
||||
|
@ -92,17 +90,15 @@ const myCustomScalarType = new GraphQLScalarType({
|
|||
});
|
||||
|
||||
const schemaString = gql`
|
||||
scalar MyCustomScalar
|
||||
|
||||
scalar MyCustomScalar
|
||||
|
||||
type Foo {
|
||||
aField: MyCustomScalar
|
||||
}
|
||||
|
||||
type Query {
|
||||
foo: Foo
|
||||
}
|
||||
type Foo {
|
||||
aField: MyCustomScalar
|
||||
}
|
||||
|
||||
type Query {
|
||||
foo: Foo
|
||||
}
|
||||
`;
|
||||
|
||||
const resolverFunctions = {
|
||||
|
@ -127,11 +123,12 @@ The goal is to define a `Date` data type for returning `Date` values from the da
|
|||
The following is the implementation of the `Date` data type. First, the schema:
|
||||
|
||||
```js
|
||||
const typeDefs = gql`scalar Date
|
||||
const typeDefs = gql`
|
||||
scalar Date
|
||||
|
||||
type MyType {
|
||||
created: Date
|
||||
}
|
||||
type MyType {
|
||||
created: Date
|
||||
}
|
||||
`
|
||||
```
|
||||
|
||||
|
@ -172,11 +169,12 @@ server.listen().then(({ url }) => {
|
|||
In this example, we follow the [official GraphQL documentation](http://graphql.org/docs/api-reference-type-system/) for the scalar datatype, which demonstrates how to validate a database field that should only contain odd numbers in GraphQL. First, the schema:
|
||||
|
||||
```js
|
||||
const typeDefs = gql`scalar Odd
|
||||
const typeDefs = gql`
|
||||
scalar Odd
|
||||
|
||||
type MyType {
|
||||
type MyType {
|
||||
oddValue: Odd
|
||||
}
|
||||
}
|
||||
`
|
||||
```
|
||||
|
||||
|
|
|
@ -21,19 +21,22 @@ Subscriptions are another root level type, similar to Query and Mutation. To sta
|
|||
|
||||
```js line=2-4
|
||||
const typeDefs = gql`
|
||||
type Subscription {
|
||||
postAdded: Post
|
||||
}
|
||||
type Query {
|
||||
posts: [Post]
|
||||
}
|
||||
type Mutation {
|
||||
addPost(author: String, comment: String): Post
|
||||
}
|
||||
type Post {
|
||||
author: String
|
||||
comment: String
|
||||
}
|
||||
type Subscription {
|
||||
postAdded: Post
|
||||
}
|
||||
|
||||
type Query {
|
||||
posts: [Post]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
addPost(author: String, comment: String): Post
|
||||
}
|
||||
|
||||
type Post {
|
||||
author: String
|
||||
comment: String
|
||||
}
|
||||
`
|
||||
```
|
||||
|
||||
|
@ -84,7 +87,7 @@ const server = new ApolloServer({
|
|||
});
|
||||
```
|
||||
|
||||
> `connection` contains various metadata, found [here](https://github.com/apollographql/subscriptions-transport-ws/blob/88970eaf6d2e3f68f98696de00631acf4062c088/src/server.ts#L312-L321).
|
||||
> `connection` contains various metadata, found [here](https://github.com/apollographql/subscriptions-transport-ws/blob/88970eaf6d2e3f68f98696de00631acf4062c088/src/server.ts#L312-L321).
|
||||
|
||||
As you can see Apollo Server 2.0 allows realtime data without invasive changes to existing code.
|
||||
For a full working example please have a look to [this repo](https://github.com/daniele-zurico/apollo2-subscriptions-how-to) provided by [Daniele Zurico](https://github.com/daniele-zurico/apollo2-subscriptions-how-to)
|
||||
|
|
|
@ -13,19 +13,20 @@ The `Union` type indicates that a field can return more than one object type, bu
|
|||
const { gql } = require('apollo-server');
|
||||
|
||||
const typeDefs = gql`
|
||||
union Result = Book | Author
|
||||
union Result = Book | Author
|
||||
|
||||
type Book {
|
||||
title: String
|
||||
}
|
||||
type Book {
|
||||
title: String
|
||||
}
|
||||
|
||||
type Author {
|
||||
name: String
|
||||
}
|
||||
type Author {
|
||||
name: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
search: [Result]
|
||||
}`;
|
||||
type Query {
|
||||
search: [Result]
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
Since a query requesting a union field, a query being made on a field which is union-typed must specify the object types containing the fields it wants. This ambiguity is solved by an extra `__resolveType` field in the resolver map. `__resolveType` defines the type of the result is out of the available options to GraphQL execution environment.
|
||||
|
|
|
@ -92,9 +92,9 @@ In Apollo server, a resolver that returns an enum can use the direct string repr
|
|||
|
||||
```js
|
||||
const schema = gql`
|
||||
type Query {
|
||||
genre: Genre
|
||||
}
|
||||
type Query {
|
||||
genre: Genre
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
@ -110,9 +110,9 @@ Lists are defined with as type modifier that wraps object types, scalars, and en
|
|||
|
||||
```js
|
||||
const schema = gql`
|
||||
type Query {
|
||||
todos: [String]
|
||||
}
|
||||
type Query {
|
||||
todos: [String]
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
@ -132,9 +132,9 @@ Using the exclamation mark to declare a field as non-nullable simplifies the con
|
|||
|
||||
```js
|
||||
const schema = gql`
|
||||
type Query {
|
||||
genre: String!
|
||||
}
|
||||
type Query {
|
||||
genre: String!
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
|
133
package-lock.json
generated
133
package-lock.json
generated
|
@ -1290,9 +1290,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/hapi": {
|
||||
"version": "17.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-17.8.0.tgz",
|
||||
"integrity": "sha512-hI713RUrMY4dn4Q1exYDg4RFYpWwgH5TLROsDFcH8j03mUqCJaorosq7OBegHFSZu0MC/W2fVqKCZ0JwT+nQ2A==",
|
||||
"version": "17.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-17.8.1.tgz",
|
||||
"integrity": "sha512-ruW5d7KiEHzkV97rzytHTgzG6zxKngx+6L+qdhYEX6qvILGX5knG/0kXNfseDhbaE7DzdS75Maf7gtgziy7i+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/boom": "*",
|
||||
|
@ -1326,9 +1326,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/joi": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.0.0.tgz",
|
||||
"integrity": "sha512-q3r+5QfNrthJL+5Pvb9MOP7rJniTfAV+cGbsO2a3ItGSuwmp2vdjjZ/VCWKwGT5lNx4eOvZI4O3IpSIXh3f4RA==",
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.0.1.tgz",
|
||||
"integrity": "sha512-0uZZ+nffpr480zwwUXsk0Z5O0szllffNW1EbkI+dDzKhNKhiX4QOwpwK37WpKIpaPLk9V8U9y2We/VOeD6zyhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-stable-stringify": {
|
||||
|
@ -1468,9 +1468,9 @@
|
|||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "10.12.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.14.tgz",
|
||||
"integrity": "sha512-0rVcFRhM93kRGAU88ASCjX9Y3FWDCh+33G5Z5evpKOea4xcpLqDGwmo64+DjgaSezTN5j9KdnUzvxhOw7fNciQ=="
|
||||
"version": "10.12.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz",
|
||||
"integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.1.4",
|
||||
|
@ -2041,31 +2041,31 @@
|
|||
}
|
||||
},
|
||||
"apollo-link": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.4.tgz",
|
||||
"integrity": "sha512-B1z+9H2nTyWEhMXRFSnoZ1vSuAYP+V/EdUJvRx9uZ8yuIBZMm6reyVtr1n0BWlKeSFyPieKJy2RLzmITAAQAMQ==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.6.tgz",
|
||||
"integrity": "sha512-sUNlA20nqIF3gG3F8eyMD+mO80fmf3dPZX+GUOs3MI9oZR8ug09H3F0UsWJMcpEg6h55Yy5wZ+BMmAjrbenF/Q==",
|
||||
"requires": {
|
||||
"apollo-utilities": "^1.0.0",
|
||||
"zen-observable-ts": "^0.8.11"
|
||||
"zen-observable-ts": "^0.8.13"
|
||||
}
|
||||
},
|
||||
"apollo-link-http": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.7.tgz",
|
||||
"integrity": "sha512-EZ9nynHjwYCpGYP5IsRrZGTWidUVpshk7MuSG4joqGtJMwpFCgMQz+y3BHdUhowHtfAd9z60XmeOTG9FJolb8A==",
|
||||
"version": "1.5.9",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.9.tgz",
|
||||
"integrity": "sha512-9tJy2zGm4Cm/1ycScDNZJe51dgnTSfKx7pKIgPZmcxkdDpgUY2DZitDH6ZBv4yp9z8MC9Xr9wgwc29s6hcadUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.4",
|
||||
"apollo-link-http-common": "^0.2.6"
|
||||
"apollo-link": "^1.2.6",
|
||||
"apollo-link-http-common": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"apollo-link-http-common": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.6.tgz",
|
||||
"integrity": "sha512-LUOMWvrZuBP1hyWLBXyaW0KyFeKo79j+k3N+Q4HSkXKbLibnllXQ+JxxoSKGhm0bhREygiLtJAG9JnGlhxGO/Q==",
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.8.tgz",
|
||||
"integrity": "sha512-gGmXZN8mr7e9zjopzKQfZ7IKnh8H12NxBDzvp9nXI3U82aCVb72p+plgoYLcpMY8w6krvoYjgicFmf8LO20TCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.4"
|
||||
"apollo-link": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"apollo-link-persisted-queries": {
|
||||
|
@ -3116,6 +3116,24 @@
|
|||
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
|
||||
"dev": true
|
||||
},
|
||||
"caller-callsite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||
"integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsites": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"caller-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
|
||||
"integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caller-callsite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||
|
@ -6931,12 +6949,12 @@
|
|||
}
|
||||
},
|
||||
"husky": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-1.2.0.tgz",
|
||||
"integrity": "sha512-/ib3+iycykXC0tYIxsyqierikVa9DA2DrT32UEirqNEFVqOj1bFMTgP3jAz8HM7FgC/C8pc/BTUa9MV2GEkZaA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-1.2.1.tgz",
|
||||
"integrity": "sha512-4Ylal3HWhnDvIszuiyLoVrSGI7QLg/ogkNCoHE34c+yZYzb9kBZNrlTOsdw92cGi3cJT8pPb6CdVfxFkLnc8Dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cosmiconfig": "^5.0.6",
|
||||
"cosmiconfig": "^5.0.7",
|
||||
"execa": "^1.0.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-stdin": "^6.0.0",
|
||||
|
@ -6948,41 +6966,16 @@
|
|||
"slash": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"cosmiconfig": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz",
|
||||
"integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
|
||||
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^6.0.0",
|
||||
"get-stream": "^4.0.0",
|
||||
"is-stream": "^1.1.0",
|
||||
"npm-run-path": "^2.0.0",
|
||||
"p-finally": "^1.0.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
||||
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pump": "^3.0.0"
|
||||
"import-fresh": "^2.0.0",
|
||||
"is-directory": "^0.3.1",
|
||||
"js-yaml": "^3.9.0",
|
||||
"parse-json": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7016,6 +7009,16 @@
|
|||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
|
||||
"integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caller-path": "^2.0.0",
|
||||
"resolve-from": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"import-local": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",
|
||||
|
@ -13359,9 +13362,9 @@
|
|||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz",
|
||||
"integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==",
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
|
||||
"integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
|
@ -13495,9 +13498,9 @@
|
|||
"integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ=="
|
||||
},
|
||||
"zen-observable-ts": {
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.11.tgz",
|
||||
"integrity": "sha512-8bs7rgGV4kz5iTb9isudkuQjtWwPnQ8lXq6/T76vrepYZVMsDEv6BXaEA+DHdJSK3KVLduagi9jSpSAJ5NgKHw==",
|
||||
"version": "0.8.13",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.13.tgz",
|
||||
"integrity": "sha512-WDb8SM0tHCb6c0l1k60qXWlm1ok3zN9U4VkLdnBKQwIYwUoB9psH7LIFgR+JVCCMmBxUgOjskIid8/N02k/2Bg==",
|
||||
"requires": {
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
|
|
12
package.json
12
package.json
|
@ -68,7 +68,7 @@
|
|||
"@types/connect": "3.4.32",
|
||||
"@types/fibers": "0.0.30",
|
||||
"@types/graphql": "14.0.3",
|
||||
"@types/hapi": "17.8.0",
|
||||
"@types/hapi": "17.8.1",
|
||||
"@types/jest": "23.3.10",
|
||||
"@types/json-stable-stringify": "1.0.32",
|
||||
"@types/koa-multer": "1.0.0",
|
||||
|
@ -78,7 +78,7 @@
|
|||
"@types/memcached": "2.2.5",
|
||||
"@types/micro": "7.3.3",
|
||||
"@types/multer": "1.3.7",
|
||||
"@types/node": "10.12.14",
|
||||
"@types/node": "10.12.15",
|
||||
"@types/node-fetch": "2.1.4",
|
||||
"@types/redis": "2.8.8",
|
||||
"@types/request": "2.48.1",
|
||||
|
@ -87,8 +87,8 @@
|
|||
"@types/type-is": "1.6.2",
|
||||
"@types/ws": "6.0.1",
|
||||
"apollo-fetch": "0.7.0",
|
||||
"apollo-link": "1.2.4",
|
||||
"apollo-link-http": "1.5.7",
|
||||
"apollo-link": "1.2.6",
|
||||
"apollo-link-http": "1.5.9",
|
||||
"apollo-link-persisted-queries": "0.2.2",
|
||||
"body-parser": "1.18.3",
|
||||
"codecov": "3.1.0",
|
||||
|
@ -101,7 +101,7 @@
|
|||
"graphql-tag": "2.10.0",
|
||||
"graphql-tools": "4.0.3",
|
||||
"hapi": "17.8.1",
|
||||
"husky": "1.2.0",
|
||||
"husky": "1.2.1",
|
||||
"jest": "23.6.0",
|
||||
"jest-junit": "5.2.0",
|
||||
"jest-matcher-utils": "23.6.0",
|
||||
|
@ -127,7 +127,7 @@
|
|||
"ts-jest": "23.10.5",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.2.2",
|
||||
"ws": "6.1.0",
|
||||
"ws": "6.1.2",
|
||||
"yup": "0.26.5"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-azure-functions",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Azure Functions",
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
jest.mock('memcached', () => require('memcached-mock'));
|
||||
|
||||
import { MemcachedCache } from '../index';
|
||||
import { testKeyValueCache } from '../../../apollo-server-caching/src/__tests__/testsuite';
|
||||
import {
|
||||
testKeyValueCache_Basics,
|
||||
testKeyValueCache_Expiration,
|
||||
} from '../../../apollo-server-caching/src/__tests__/testsuite';
|
||||
|
||||
testKeyValueCache(new MemcachedCache('localhost'));
|
||||
describe('Memcached', () => {
|
||||
const cache = new MemcachedCache('localhost');
|
||||
testKeyValueCache_Basics(cache);
|
||||
testKeyValueCache_Expiration(cache);
|
||||
});
|
||||
|
|
|
@ -22,11 +22,11 @@ export class MemcachedCache implements KeyValueCache {
|
|||
|
||||
async set(
|
||||
key: string,
|
||||
data: string,
|
||||
value: string,
|
||||
options?: { ttl?: number },
|
||||
): Promise<void> {
|
||||
const { ttl } = Object.assign({}, this.defaultSetOptions, options);
|
||||
await this.client.set(key, data, ttl);
|
||||
await this.client.set(key, value, ttl);
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | undefined> {
|
||||
|
|
|
@ -3,6 +3,13 @@ jest.mock('redis', () => require('redis-mock'));
|
|||
jest.useFakeTimers(); // mocks out setTimeout that is used in redis-mock
|
||||
|
||||
import { RedisCache } from '../index';
|
||||
import { testKeyValueCache } from '../../../apollo-server-caching/src/__tests__/testsuite';
|
||||
import {
|
||||
testKeyValueCache_Basics,
|
||||
testKeyValueCache_Expiration,
|
||||
} from '../../../apollo-server-caching/src/__tests__/testsuite';
|
||||
|
||||
testKeyValueCache(new RedisCache({ host: 'localhost' }));
|
||||
describe('Redis', () => {
|
||||
const cache = new RedisCache({ host: 'localhost' });
|
||||
testKeyValueCache_Basics(cache);
|
||||
testKeyValueCache_Expiration(cache);
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import Redis from 'redis';
|
|||
import { promisify } from 'util';
|
||||
import DataLoader from 'dataloader';
|
||||
|
||||
export class RedisCache implements KeyValueCache {
|
||||
export class RedisCache implements KeyValueCache<string> {
|
||||
// FIXME: Replace any with proper promisified type
|
||||
readonly client: any;
|
||||
readonly defaultSetOptions = {
|
||||
|
@ -31,11 +31,11 @@ export class RedisCache implements KeyValueCache {
|
|||
|
||||
async set(
|
||||
key: string,
|
||||
data: string,
|
||||
value: string,
|
||||
options?: { ttl?: number },
|
||||
): Promise<void> {
|
||||
const { ttl } = Object.assign({}, this.defaultSetOptions, options);
|
||||
await this.client.set(key, data, 'EX', ttl);
|
||||
await this.client.set(key, value, 'EX', ttl);
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | undefined> {
|
||||
|
|
|
@ -16,7 +16,9 @@ export interface KeyValueCache {
|
|||
}
|
||||
```
|
||||
|
||||
## Running test suite
|
||||
## Testing cache implementations
|
||||
|
||||
### Test helpers
|
||||
|
||||
You can export and run a jest test suite from `apollo-server-caching` to test your implementation:
|
||||
|
||||
|
@ -28,4 +30,15 @@ import { testKeyValueCache } from 'apollo-server-caching';
|
|||
testKeyValueCache(new MemcachedCache('localhost'));
|
||||
```
|
||||
|
||||
The default `testKeyValueCache` helper will run all key-value store tests on the specified store, including basic `get` and `set` functionality, along with time-based expunging rules.
|
||||
|
||||
Some key-value cache implementations may not be able to support the full suite of tests (for example, some tests might not be able to expire based on time). For those cases, there are more granular implementations which can be used:
|
||||
|
||||
* `testKeyValueCache_Basic`
|
||||
* `testKeyValueCache_Expiration`
|
||||
|
||||
For more details, consult the [source for `apollo-server-caching`](./src/__tests__/testsuite.ts).
|
||||
|
||||
### Running tests
|
||||
|
||||
Run tests with `jest --verbose`
|
||||
|
|
|
@ -1,24 +1,46 @@
|
|||
import LRU from 'lru-cache';
|
||||
import { KeyValueCache } from './KeyValueCache';
|
||||
|
||||
export class InMemoryLRUCache implements KeyValueCache {
|
||||
private store: LRU.Cache<string, string>;
|
||||
export class InMemoryLRUCache<V = string> implements KeyValueCache<V> {
|
||||
private store: LRU.Cache<string, V>;
|
||||
|
||||
// FIXME: Define reasonable default max size of the cache
|
||||
constructor({ maxSize = Infinity }: { maxSize?: number } = {}) {
|
||||
this.store = new LRU({
|
||||
max: maxSize,
|
||||
length: item => item.length,
|
||||
length(item) {
|
||||
if (Array.isArray(item) || typeof item === 'string') {
|
||||
return item.length;
|
||||
}
|
||||
|
||||
// If it's an object, we'll use the length to get an approximate,
|
||||
// relative size of what it would take to store it. It's certainly not
|
||||
// 100% accurate, but it's a very, very fast implementation and it
|
||||
// doesn't require bringing in other dependencies or logic which we need
|
||||
// to maintain. In the future, we might consider something like:
|
||||
// npm.im/object-sizeof, but this should be sufficient for now.
|
||||
if (typeof item === 'object') {
|
||||
return JSON.stringify(item).length;
|
||||
}
|
||||
|
||||
// Go with the lru-cache default "naive" size, in lieu anything better:
|
||||
// https://github.com/isaacs/node-lru-cache/blob/a71be6cd/index.js#L17
|
||||
return 1;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
return this.store.get(key);
|
||||
}
|
||||
async set(key: string, value: string) {
|
||||
this.store.set(key, value);
|
||||
async set(key: string, value: V, options?: { ttl?: number }) {
|
||||
const maxAge = options && options.ttl && options.ttl * 1000;
|
||||
this.store.set(key, value, maxAge);
|
||||
}
|
||||
async delete(key: string) {
|
||||
this.store.del(key);
|
||||
}
|
||||
async flush(): Promise<void> {
|
||||
this.store.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export interface KeyValueCache {
|
||||
get(key: string): Promise<string | undefined>;
|
||||
set(key: string, value: string, options?: { ttl?: number }): Promise<void>;
|
||||
export interface KeyValueCache<V = string> {
|
||||
get(key: string): Promise<V | undefined>;
|
||||
set(key: string, value: V, options?: { ttl?: number }): Promise<void>;
|
||||
delete(key: string): Promise<boolean | void>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {
|
||||
testKeyValueCache_Basics,
|
||||
testKeyValueCache_Expiration,
|
||||
} from '../../../apollo-server-caching/src/__tests__/testsuite';
|
||||
import { InMemoryLRUCache } from '../InMemoryLRUCache';
|
||||
|
||||
describe('InMemoryLRUCache', () => {
|
||||
const cache = new InMemoryLRUCache();
|
||||
testKeyValueCache_Basics(cache);
|
||||
testKeyValueCache_Expiration(cache);
|
||||
});
|
|
@ -1,21 +1,11 @@
|
|||
import { advanceTimeBy, mockDate, unmockDate } from '__mocks__/date';
|
||||
|
||||
export function testKeyValueCache(keyValueCache: any) {
|
||||
describe('KeyValueCache Test Suite', () => {
|
||||
beforeAll(() => {
|
||||
mockDate();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
export function testKeyValueCache_Basics(keyValueCache: any) {
|
||||
describe('basic cache functionality', () => {
|
||||
beforeEach(() => {
|
||||
keyValueCache.flush();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
unmockDate();
|
||||
keyValueCache.close();
|
||||
});
|
||||
|
||||
it('can do a basic get and set', async () => {
|
||||
await keyValueCache.set('hello', 'world');
|
||||
expect(await keyValueCache.get('hello')).toBe('world');
|
||||
|
@ -28,6 +18,24 @@ export function testKeyValueCache(keyValueCache: any) {
|
|||
await keyValueCache.delete('hello');
|
||||
expect(await keyValueCache.get('hello')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function testKeyValueCache_Expiration(keyValueCache: any) {
|
||||
describe('time-based cache expunging', () => {
|
||||
beforeAll(() => {
|
||||
mockDate();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
keyValueCache.flush();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
unmockDate();
|
||||
keyValueCache.close();
|
||||
});
|
||||
|
||||
it('is able to expire keys based on ttl', async () => {
|
||||
await keyValueCache.set('short', 's', { ttl: 1 });
|
||||
|
@ -45,3 +53,10 @@ export function testKeyValueCache(keyValueCache: any) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function testKeyValueCache(keyValueCache: any) {
|
||||
describe('KeyValueCache Test Suite', () => {
|
||||
testKeyValueCache_Basics(keyValueCache);
|
||||
testKeyValueCache_Expiration(keyValueCache);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-cloud-functions",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Google Cloud Functions",
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-cloudflare",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Cloudflare workers",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-core",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Core engine for Apollo GraphQL server",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -9,8 +9,6 @@ import {
|
|||
GraphQLNonNull,
|
||||
parse,
|
||||
DocumentNode,
|
||||
ValidationContext,
|
||||
GraphQLFieldResolver,
|
||||
} from 'graphql';
|
||||
|
||||
import {
|
||||
|
@ -19,10 +17,9 @@ import {
|
|||
GraphQLResponse,
|
||||
} from 'graphql-extensions';
|
||||
|
||||
import { CacheControlExtensionOptions } from 'apollo-cache-control';
|
||||
|
||||
import { processGraphQLRequest, GraphQLRequest } from '../requestPipeline';
|
||||
import { Request } from 'apollo-server-env';
|
||||
import { GraphQLOptions, Context as GraphQLContext } from 'apollo-server-core';
|
||||
|
||||
// This is a temporary kludge to ensure we preserve runQuery behavior with the
|
||||
// GraphQLRequestProcessor refactoring.
|
||||
|
@ -46,25 +43,26 @@ function runQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
|||
});
|
||||
}
|
||||
|
||||
interface QueryOptions {
|
||||
schema: GraphQLSchema;
|
||||
|
||||
interface QueryOptions
|
||||
extends Pick<
|
||||
GraphQLOptions<GraphQLContext<any>>,
|
||||
| 'cacheControl'
|
||||
| 'context'
|
||||
| 'debug'
|
||||
| 'extensions'
|
||||
| 'fieldResolver'
|
||||
| 'formatError'
|
||||
| 'formatResponse'
|
||||
| 'rootValue'
|
||||
| 'schema'
|
||||
| 'tracing'
|
||||
| 'validationRules'
|
||||
> {
|
||||
queryString?: string;
|
||||
parsedQuery?: DocumentNode;
|
||||
|
||||
rootValue?: any;
|
||||
context?: any;
|
||||
variables?: { [key: string]: any };
|
||||
operationName?: string;
|
||||
validationRules?: Array<(context: ValidationContext) => any>;
|
||||
fieldResolver?: GraphQLFieldResolver<any, any>;
|
||||
formatError?: Function;
|
||||
formatResponse?: Function;
|
||||
debug?: boolean;
|
||||
tracing?: boolean;
|
||||
cacheControl?: CacheControlExtensionOptions;
|
||||
request: Pick<Request, 'url' | 'method' | 'headers'>;
|
||||
extensions?: Array<() => GraphQLExtension>;
|
||||
}
|
||||
|
||||
const queryType = new GraphQLObjectType({
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/// <reference path="./types/graphql-upload.d.ts" />
|
||||
|
||||
import supportsUploadsInNode from './utils/supportsUploadsInNode';
|
||||
|
||||
// We'll memoize this function once at module load time since it should never
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from 'apollo-cache-control';
|
||||
import { TracingExtension } from 'apollo-tracing';
|
||||
import {
|
||||
ApolloError,
|
||||
fromGraphQLError,
|
||||
SyntaxError,
|
||||
ValidationError,
|
||||
|
@ -135,15 +136,9 @@ export async function processGraphQLRequest<TContext>(
|
|||
|
||||
persistedQueryRegister = true;
|
||||
|
||||
// Store the query asynchronously so we don't block.
|
||||
(async () => {
|
||||
return (
|
||||
config.persistedQueries &&
|
||||
config.persistedQueries.cache.set(`apq:${queryHash}`, query)
|
||||
);
|
||||
})().catch(error => {
|
||||
console.warn(error);
|
||||
});
|
||||
Promise.resolve(
|
||||
config.persistedQueries.cache.set(`apq:${queryHash}`, query),
|
||||
).catch(console.warn);
|
||||
}
|
||||
} else if (query) {
|
||||
// FIXME: We'll compute the APQ query hash to use as our cache key for
|
||||
|
@ -179,13 +174,7 @@ export async function processGraphQLRequest<TContext>(
|
|||
parsingDidEnd();
|
||||
} catch (syntaxError) {
|
||||
parsingDidEnd(syntaxError);
|
||||
return sendResponse({
|
||||
errors: [
|
||||
fromGraphQLError(syntaxError, {
|
||||
errorClass: SyntaxError,
|
||||
}),
|
||||
],
|
||||
});
|
||||
return sendErrorResponse(syntaxError, SyntaxError);
|
||||
}
|
||||
|
||||
requestContext.document = document;
|
||||
|
@ -197,19 +186,13 @@ export async function processGraphQLRequest<TContext>(
|
|||
|
||||
const validationErrors = validate(document);
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
if (validationErrors.length === 0) {
|
||||
validationDidEnd();
|
||||
} else {
|
||||
validationDidEnd(validationErrors);
|
||||
return sendResponse({
|
||||
errors: validationErrors.map(validationError =>
|
||||
fromGraphQLError(validationError, {
|
||||
errorClass: ValidationError,
|
||||
}),
|
||||
),
|
||||
});
|
||||
return sendErrorResponse(validationErrors, ValidationError);
|
||||
}
|
||||
|
||||
validationDidEnd();
|
||||
|
||||
// FIXME: If we want to guarantee an operation has been set when invoking
|
||||
// `willExecuteOperation` and executionDidStart`, we need to throw an
|
||||
// error here and not leave this to `buildExecutionContext` in
|
||||
|
@ -248,9 +231,7 @@ export async function processGraphQLRequest<TContext>(
|
|||
executionDidEnd();
|
||||
} catch (executionError) {
|
||||
executionDidEnd(executionError);
|
||||
return sendResponse({
|
||||
errors: [fromGraphQLError(executionError)],
|
||||
});
|
||||
return sendErrorResponse(executionError);
|
||||
}
|
||||
|
||||
const formattedExtensions = extensionStack.format();
|
||||
|
@ -346,6 +327,27 @@ export async function processGraphQLRequest<TContext>(
|
|||
return requestContext.response!;
|
||||
}
|
||||
|
||||
function sendErrorResponse(
|
||||
errorOrErrors: ReadonlyArray<GraphQLError> | GraphQLError,
|
||||
errorClass?: typeof ApolloError,
|
||||
) {
|
||||
// If a single error is passed, it should still be encapsulated in an array.
|
||||
const errors = Array.isArray(errorOrErrors)
|
||||
? errorOrErrors
|
||||
: [errorOrErrors];
|
||||
|
||||
return sendResponse({
|
||||
errors: errors.map(err =>
|
||||
fromGraphQLError(
|
||||
err,
|
||||
errorClass && {
|
||||
errorClass,
|
||||
},
|
||||
),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function initializeRequestListenerDispatcher(): Dispatcher<
|
||||
GraphQLRequestListener
|
||||
> {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-express",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Express and Connect",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-hapi",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Hapi",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "apollo-server-integration-testsuite",
|
||||
"private": true,
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Apollo Server Integrations testsuite",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -978,7 +978,7 @@ export function testApolloServer<AS extends ApolloServerBase>(
|
|||
});
|
||||
});
|
||||
});
|
||||
it('disables subscritpions when option set to false', done => {
|
||||
it('disables subscriptions when option set to false', done => {
|
||||
const typeDefs = gql`
|
||||
type Query {
|
||||
"graphql-js forces there to be a query type"
|
||||
|
@ -1025,7 +1025,7 @@ export function testApolloServer<AS extends ApolloServerBase>(
|
|||
}
|
||||
|
||||
const client = new SubscriptionClient(
|
||||
`ws://localhost:${port}${server.subscriptionsPath}`,
|
||||
`ws://localhost:${port}${server.subscriptionsPath || ''}`,
|
||||
{},
|
||||
WebSocket,
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-koa",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Koa",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-lambda",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for AWS Lambda",
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-micro",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Micro",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-plugin-base",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "Apollo Server plugin base classes",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-testing",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Test utils for apollo-server",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Production ready GraphQL Server",
|
||||
"author": "opensource@apollographql.com",
|
||||
"main": "dist/index.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "graphql-extensions",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"description": "Add extensions to GraphQL servers",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
|
Loading…
Add table
Reference in a new issue