mirror of
https://github.com/vale981/apollo-server
synced 2025-03-04 09:11:40 -05:00
Merge branch 'master' into re-stage-pr-1971
This commit is contained in:
commit
8ac2884cfa
45 changed files with 4524 additions and 1971 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -2,11 +2,22 @@
|
|||
|
||||
### vNEXT
|
||||
|
||||
- Move `apollo-graphql` package to the `apollo-tooling` repository [PR #2316](https://github.com/apollographql/apollo-server/pull/2316)
|
||||
|
||||
### v2.4.1
|
||||
|
||||
- Fix inaccurate total duration in apollo-tracing [PR #2298](https://github.com/apollographql/apollo-server/pull/2298)
|
||||
- Avoid importing entire `crypto` dependency tree if not in Node.js. [PR #2304](https://github.com/apollographql/apollo-server/pull/2304)
|
||||
- Allow passing `parseOptions` to `ApolloServerBase` constructor. [PR #2289](https://github.com/apollographql/apollo-server/pull/2289)
|
||||
- Rename `azureFunctions.d.ts` to `azureFunctions.ts`. [PR #2287](https://github.com/apollographql/apollo-server/pull/2287)
|
||||
- Require `apollo-engine-reporting` only if `EngineReportingAgent` used. [PR #2305](https://github.com/apollographql/apollo-server/pull/2305)
|
||||
|
||||
### v2.4.0
|
||||
|
||||
- Implement an in-memory cache store to save parsed and validated documents and provide performance benefits for repeat executions of the same document. [PR #2111](https://github.com/apollographql/apollo-server/pull/2111) (`>=2.4.0-alpha.0`)
|
||||
- Fix: Serialize arrays as JSON on fetch in `RESTDataSource`. [PR #2219](https://github.com/apollographql/apollo-server/pull/2219)
|
||||
- Fix: The `privateHeaders` configuration for `apollo-engine-reporting` now allows headers to be specified using any case and lower-cases them prior to comparison. [PR #2276](https://github.com/apollographql/apollo-server/pull/2276)
|
||||
- Fix broken `apollo-server-azure-functions` TypeScript definitions. [PR #2287](https://github.com/apollographql/apollo-server/pull/2287)
|
||||
|
||||
### v2.3.3
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = {
|
|||
// We don't want to match `apollo-server-env` and
|
||||
// `apollo-engine-reporting-protobuf`, because these don't depend on
|
||||
// compilation but need to be initialized from as parto of `prepare`.
|
||||
'^(?!apollo-server-env|apollo-engine-reporting-protobuf)(apollo-(?:server|graphql|datasource|cache-control|tracing|engine)[^/]*|graphql-extensions)(?:/dist)?((?:/.*)|$)': '<rootDir>/../../packages/$1/src$2'
|
||||
'^(?!apollo-server-env|apollo-engine-reporting-protobuf)(apollo-(?:server|datasource|cache-control|tracing|engine)[^/]*|graphql-extensions)(?:/dist)?((?:/.*)|$)': '<rootDir>/../../packages/$1/src$2'
|
||||
},
|
||||
clearMocks: true,
|
||||
globals: {
|
||||
|
|
5620
package-lock.json
generated
5620
package-lock.json
generated
File diff suppressed because it is too large
Load diff
29
package.json
29
package.json
|
@ -34,13 +34,12 @@
|
|||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollographql/apollo-tools": "^0.3.0",
|
||||
"@apollographql/apollo-tools": "^0.3.3",
|
||||
"apollo-cache-control": "file:packages/apollo-cache-control",
|
||||
"apollo-datasource": "file:packages/apollo-datasource",
|
||||
"apollo-datasource-rest": "file:packages/apollo-datasource-rest",
|
||||
"apollo-engine-reporting": "file:packages/apollo-engine-reporting",
|
||||
"apollo-engine-reporting-protobuf": "file:packages/apollo-engine-reporting-protobuf",
|
||||
"apollo-graphql": "file:packages/apollo-graphql",
|
||||
"apollo-server": "file:packages/apollo-server",
|
||||
"apollo-server-azure-functions": "file:packages/apollo-server-azure-functions",
|
||||
"apollo-server-cache-memcached": "file:packages/apollo-server-cache-memcached",
|
||||
|
@ -65,24 +64,24 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/async-retry": "1.2.1",
|
||||
"@types/aws-lambda": "8.10.19",
|
||||
"@types/aws-lambda": "8.10.18",
|
||||
"@types/body-parser": "1.17.0",
|
||||
"@types/connect": "3.4.32",
|
||||
"@types/fast-json-stable-stringify": "2.0.0",
|
||||
"@types/fibers": "0.0.30",
|
||||
"@types/graphql": "14.0.5",
|
||||
"@types/graphql": "14.0.7",
|
||||
"@types/hapi": "17.8.5",
|
||||
"@types/jest": "23.3.14",
|
||||
"@types/jest": "24.0.4",
|
||||
"@types/koa-multer": "1.0.0",
|
||||
"@types/koa-router": "7.0.39",
|
||||
"@types/lodash": "4.14.120",
|
||||
"@types/lodash.sortby": "4.7.4",
|
||||
"@types/lodash": "4.14.121",
|
||||
"@types/lodash.sortby": "4.7.5",
|
||||
"@types/lru-cache": "4.1.1",
|
||||
"@types/memcached": "2.2.5",
|
||||
"@types/memcached": "2.2.6",
|
||||
"@types/micro": "7.3.3",
|
||||
"@types/multer": "1.3.7",
|
||||
"@types/node": "10.12.23",
|
||||
"@types/node-fetch": "2.1.5",
|
||||
"@types/node": "10.12.26",
|
||||
"@types/node-fetch": "2.1.6",
|
||||
"@types/redis": "2.8.10",
|
||||
"@types/request": "2.48.1",
|
||||
"@types/request-promise": "4.1.42",
|
||||
|
@ -94,7 +93,7 @@
|
|||
"apollo-link-http": "1.5.11",
|
||||
"apollo-link-persisted-queries": "0.2.2",
|
||||
"body-parser": "1.18.3",
|
||||
"codecov": "3.1.0",
|
||||
"codecov": "3.2.0",
|
||||
"connect": "3.6.6",
|
||||
"express": "4.16.4",
|
||||
"fastify": "1.13.0",
|
||||
|
@ -106,14 +105,14 @@
|
|||
"graphql-tools": "4.0.4",
|
||||
"hapi": "17.8.4",
|
||||
"husky": "1.3.1",
|
||||
"jest": "23.6.0",
|
||||
"jest": "24.1.0",
|
||||
"jest-junit": "5.2.0",
|
||||
"jest-matcher-utils": "23.6.0",
|
||||
"jest-matcher-utils": "24.0.0",
|
||||
"js-sha256": "0.9.0",
|
||||
"koa": "2.7.0",
|
||||
"koa-multer": "1.0.2",
|
||||
"lerna": "3.11.0",
|
||||
"lint-staged": "8.1.3",
|
||||
"lerna": "3.11.1",
|
||||
"lint-staged": "8.1.4",
|
||||
"memcached-mock": "0.1.0",
|
||||
"meteor-promise": "0.8.7",
|
||||
"mock-req": "0.2.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-engine-reporting",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Send reports about your GraphQL services to Apollo Engine",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
@ -12,10 +12,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"apollo-engine-reporting-protobuf": "file:../apollo-engine-reporting-protobuf",
|
||||
"apollo-graphql": "file:../apollo-graphql",
|
||||
"apollo-server-core": "file:../apollo-server-core",
|
||||
"apollo-server-env": "file:../apollo-server-env",
|
||||
"graphql-extensions": "file:../graphql-extensions",
|
||||
"async-retry": "^1.2.1",
|
||||
"graphql-extensions": "file:../graphql-extensions"
|
||||
"apollo-graphql": "^0.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"exclude": ["**/__tests__", "**/__mocks__"],
|
||||
"references": [
|
||||
{ "path": "../graphql-extensions" },
|
||||
{ "path": "../apollo-graphql" },
|
||||
{ "path": "../apollo-server-core/tsconfig.requestPipelineAPI.json" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
*
|
||||
!src/**/*
|
||||
!dist/**/*
|
||||
dist/**/*.test.*
|
||||
!package.json
|
||||
!README.md
|
|
@ -1,4 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
### vNEXT
|
||||
|
|
@ -1 +0,0 @@
|
|||
# `apollo-graphql`
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "apollo-graphql",
|
||||
"version": "0.1.0",
|
||||
"description": "Apollo GraphQL utility library",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"keywords": [],
|
||||
"author": "Apollo <opensource@apollographql.com>",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash.sortby": "^4.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0"
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
import { DocumentNode } from 'graphql';
|
||||
import { default as gql, disableFragmentWarnings } from 'graphql-tag';
|
||||
|
||||
import {
|
||||
printWithReducedWhitespace,
|
||||
hideLiterals,
|
||||
dropUnusedDefinitions,
|
||||
sortAST,
|
||||
removeAliases,
|
||||
} from '../transforms';
|
||||
|
||||
// The gql duplicate fragment warning feature really is just warnings; nothing
|
||||
// breaks if you turn it off in tests.
|
||||
disableFragmentWarnings();
|
||||
|
||||
describe('aggressive signature', () => {
|
||||
function aggressive(ast: DocumentNode, operationName: string): string {
|
||||
return printWithReducedWhitespace(
|
||||
removeAliases(
|
||||
hideLiterals(sortAST(dropUnusedDefinitions(ast, operationName))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const cases = [
|
||||
// Test cases borrowed from optics-agent-js.
|
||||
{
|
||||
name: 'basic test',
|
||||
operationName: '',
|
||||
input: gql`
|
||||
{
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
output: '{user{name}}',
|
||||
},
|
||||
{
|
||||
name: 'basic test with query',
|
||||
operationName: '',
|
||||
input: gql`
|
||||
query {
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
output: '{user{name}}',
|
||||
},
|
||||
{
|
||||
name: 'basic with operation name',
|
||||
operationName: 'OpName',
|
||||
input: gql`
|
||||
query OpName {
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
output: 'query OpName{user{name}}',
|
||||
},
|
||||
{
|
||||
name: 'with various inline types',
|
||||
operationName: 'OpName',
|
||||
input: gql`
|
||||
query OpName {
|
||||
user {
|
||||
name(apple: [[10]], cat: ENUM_VALUE, bag: { input: "value" })
|
||||
}
|
||||
}
|
||||
`,
|
||||
output: 'query OpName{user{name(apple:[],bag:{},cat:ENUM_VALUE)}}',
|
||||
},
|
||||
{
|
||||
name: 'with various argument types',
|
||||
operationName: 'OpName',
|
||||
input: gql`
|
||||
query OpName($c: Int!, $a: [[Boolean!]!], $b: EnumType) {
|
||||
user {
|
||||
name(apple: $a, cat: $c, bag: $b)
|
||||
}
|
||||
}
|
||||
`,
|
||||
output:
|
||||
'query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!){user{name(apple:$a,bag:$b,cat:$c)}}',
|
||||
},
|
||||
{
|
||||
name: 'fragment',
|
||||
operationName: '',
|
||||
input: gql`
|
||||
{
|
||||
user {
|
||||
name
|
||||
...Bar
|
||||
}
|
||||
}
|
||||
|
||||
fragment Bar on User {
|
||||
asd
|
||||
}
|
||||
|
||||
fragment Baz on User {
|
||||
jkl
|
||||
}
|
||||
`,
|
||||
output: '{user{name...Bar}}fragment Bar on User{asd}',
|
||||
},
|
||||
{
|
||||
name: 'full test',
|
||||
operationName: 'Foo',
|
||||
input: gql`
|
||||
query Foo($b: Int, $a: Boolean) {
|
||||
user(name: "hello", age: 5) {
|
||||
...Bar
|
||||
... on User {
|
||||
hello
|
||||
bee
|
||||
}
|
||||
tz
|
||||
aliased: name
|
||||
}
|
||||
}
|
||||
|
||||
fragment Baz on User {
|
||||
asd
|
||||
}
|
||||
|
||||
fragment Bar on User {
|
||||
age @skip(if: $a)
|
||||
...Nested
|
||||
}
|
||||
|
||||
fragment Nested on User {
|
||||
blah
|
||||
}
|
||||
`,
|
||||
output:
|
||||
'query Foo($a:Boolean,$b:Int){user(age:0,name:""){name tz...Bar...on User{bee hello}}}' +
|
||||
'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}',
|
||||
},
|
||||
];
|
||||
cases.forEach(({ name, operationName, input, output }) => {
|
||||
test(name, () => {
|
||||
expect(aggressive(input, operationName)).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
import { default as gql, disableFragmentWarnings } from 'graphql-tag';
|
||||
|
||||
import { printWithReducedWhitespace, hideLiterals } from '../transforms';
|
||||
|
||||
// The gql duplicate fragment warning feature really is just warnings; nothing
|
||||
// breaks if you turn it off in tests.
|
||||
disableFragmentWarnings();
|
||||
|
||||
describe('printWithReducedWhitespace', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'lots of whitespace',
|
||||
// Note: there's a tab after "tab->", which prettier wants to keep as a
|
||||
// literal tab rather than \t. In the output, there should be a literal
|
||||
// backslash-t.
|
||||
input: gql`
|
||||
query Foo($a: Int) {
|
||||
user(
|
||||
name: " tab-> yay"
|
||||
other: """
|
||||
apple
|
||||
bag
|
||||
cat
|
||||
"""
|
||||
) {
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
output:
|
||||
'query Foo($a:Int){user(name:" tab->\\tyay",other:"apple\\n bag\\ncat"){name}}',
|
||||
},
|
||||
];
|
||||
cases.forEach(({ name, input, output }) => {
|
||||
test(name, () => {
|
||||
expect(printWithReducedWhitespace(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideLiterals', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'full test',
|
||||
input: gql`
|
||||
query Foo($b: Int, $a: Boolean) {
|
||||
user(name: "hello", age: 5) {
|
||||
...Bar
|
||||
... on User {
|
||||
hello
|
||||
bee
|
||||
}
|
||||
tz
|
||||
aliased: name
|
||||
}
|
||||
}
|
||||
|
||||
fragment Bar on User {
|
||||
age @skip(if: $a)
|
||||
...Nested
|
||||
}
|
||||
|
||||
fragment Nested on User {
|
||||
blah
|
||||
}
|
||||
`,
|
||||
output:
|
||||
'query Foo($b:Int,$a:Boolean){user(name:"",age:0){...Bar...on User{hello bee}tz aliased:name}}' +
|
||||
'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}',
|
||||
},
|
||||
];
|
||||
cases.forEach(({ name, input, output }) => {
|
||||
test(name, () => {
|
||||
expect(printWithReducedWhitespace(hideLiterals(input))).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
export { defaultEngineReportingSignature } from './signature';
|
|
@ -1,68 +0,0 @@
|
|||
// In Engine, we want to group requests making the same query together, and
|
||||
// treat different queries distinctly. But what does it mean for two queries to
|
||||
// be "the same"? And what if you don't want to send the full text of the query
|
||||
// to Apollo Engine's servers, either because it contains sensitive data or
|
||||
// because it contains extraneous operations or fragments?
|
||||
//
|
||||
// To solve these problems, EngineReportingAgent has the concept of
|
||||
// "signatures". We don't (by default) send the full query string of queries to
|
||||
// the Engine servers. Instead, each trace has its query string's "signature".
|
||||
//
|
||||
// You can specify any function mapping a GraphQL query AST (DocumentNode) to
|
||||
// string as your signature algorithm by providing it as the 'signature' option
|
||||
// to the EngineReportingAgent constructor. Ideally, your signature should be a
|
||||
// valid GraphQL query, though as of now the Engine servers do not re-parse your
|
||||
// signature and do not expect it to match the execution tree in the trace.
|
||||
//
|
||||
// This module utilizes several AST transformations from the adjacent
|
||||
// 'transforms' module (which are also for writing your own signature method).
|
||||
|
||||
// - dropUnusedDefinitions, which removes operations and fragments that
|
||||
// aren't going to be used in execution
|
||||
// - hideLiterals, which replaces all numeric and string literals as well
|
||||
// as list and object input values with "empty" values
|
||||
// - removeAliases, which removes field aliasing from the query
|
||||
// - sortAST, which sorts the children of most multi-child nodes
|
||||
// consistently
|
||||
// - printWithReducedWhitespace, a variant on graphql-js's 'print'
|
||||
// which gets rid of unneeded whitespace
|
||||
//
|
||||
// defaultSignature consists of applying all of these building blocks.
|
||||
//
|
||||
// Historical note: the default signature algorithm of the Go engineproxy
|
||||
// performed all of the above operations, and the Engine servers then re-ran a
|
||||
// mostly identical signature implementation on received traces. This was
|
||||
// primarily to deal with edge cases where some users used literal interpolation
|
||||
// instead of GraphQL variables, included randomized alias names, etc. In
|
||||
// addition, the servers relied on the fact that dropUnusedDefinitions had been
|
||||
// called in order (and that the signature could be parsed as GraphQL) to
|
||||
// extract the name of the operation for display. This caused confusion, as the
|
||||
// query document shown in the Engine UI wasn't the same as the one actually
|
||||
// sent. apollo-engine-reporting uses a new reporting API which requires it to
|
||||
// explicitly include the operation name with each signature; this means that
|
||||
// the server no longer needs to parse the signature or run its own signature
|
||||
// algorithm on it, and the details of the signature algorithm are now up to the
|
||||
// reporting agent.
|
||||
|
||||
import { DocumentNode } from 'graphql';
|
||||
import {
|
||||
printWithReducedWhitespace,
|
||||
dropUnusedDefinitions,
|
||||
removeAliases,
|
||||
sortAST,
|
||||
hideLiterals,
|
||||
} from './transforms';
|
||||
|
||||
// The default signature function consists of removing unused definitions
|
||||
// and whitespace.
|
||||
// XXX consider caching somehow
|
||||
export function defaultEngineReportingSignature(
|
||||
ast: DocumentNode,
|
||||
operationName: string,
|
||||
): string {
|
||||
return printWithReducedWhitespace(
|
||||
sortAST(
|
||||
removeAliases(hideLiterals(dropUnusedDefinitions(ast, operationName))),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
import { visit } from 'graphql/language/visitor';
|
||||
import {
|
||||
DocumentNode,
|
||||
FloatValueNode,
|
||||
IntValueNode,
|
||||
StringValueNode,
|
||||
OperationDefinitionNode,
|
||||
SelectionSetNode,
|
||||
FragmentSpreadNode,
|
||||
InlineFragmentNode,
|
||||
DirectiveNode,
|
||||
FieldNode,
|
||||
FragmentDefinitionNode,
|
||||
ObjectValueNode,
|
||||
ListValueNode,
|
||||
} from 'graphql/language/ast';
|
||||
import { print } from 'graphql/language/printer';
|
||||
import { separateOperations } from 'graphql/utilities';
|
||||
// We'll only fetch the `ListIteratee` type from the `@types/lodash`, but get
|
||||
// `sortBy` from the modularized version of the package to avoid bringing in
|
||||
// all of `lodash`.
|
||||
import { ListIteratee } from 'lodash';
|
||||
import sortBy from 'lodash.sortby';
|
||||
|
||||
// Replace numeric, string, list, and object literals with "empty"
|
||||
// values. Leaves enums alone (since there's no consistent "zero" enum). This
|
||||
// can help combine similar queries if you substitute values directly into
|
||||
// queries rather than use GraphQL variables, and can hide sensitive data in
|
||||
// your query (say, a hardcoded API key) from Engine servers, but in general
|
||||
// avoiding those situations is better than working around them.
|
||||
export function hideLiterals(ast: DocumentNode): DocumentNode {
|
||||
return visit(ast, {
|
||||
IntValue(node: IntValueNode): IntValueNode {
|
||||
return { ...node, value: '0' };
|
||||
},
|
||||
FloatValue(node: FloatValueNode): FloatValueNode {
|
||||
return { ...node, value: '0' };
|
||||
},
|
||||
StringValue(node: StringValueNode): StringValueNode {
|
||||
return { ...node, value: '', block: false };
|
||||
},
|
||||
ListValue(node: ListValueNode): ListValueNode {
|
||||
return { ...node, values: [] };
|
||||
},
|
||||
ObjectValue(node: ObjectValueNode): ObjectValueNode {
|
||||
return { ...node, fields: [] };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// In the same spirit as the similarly named `hideLiterals` function, only
|
||||
// hide string and numeric literals.
|
||||
export function hideStringAndNumericLiterals(ast: DocumentNode): DocumentNode {
|
||||
return visit(ast, {
|
||||
IntValue(node: IntValueNode): IntValueNode {
|
||||
return { ...node, value: '0' };
|
||||
},
|
||||
FloatValue(node: FloatValueNode): FloatValueNode {
|
||||
return { ...node, value: '0' };
|
||||
},
|
||||
StringValue(node: StringValueNode): StringValueNode {
|
||||
return { ...node, value: '', block: false };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// A GraphQL query may contain multiple named operations, with the operation to
|
||||
// use specified separately by the client. This transformation drops unused
|
||||
// operations from the query, as well as any fragment definitions that are not
|
||||
// referenced. (In general we recommend that unused definitions are dropped on
|
||||
// the client before sending to the server to save bandwidth and parsing time.)
|
||||
export function dropUnusedDefinitions(
|
||||
ast: DocumentNode,
|
||||
operationName: string,
|
||||
): DocumentNode {
|
||||
const separated = separateOperations(ast)[operationName];
|
||||
if (!separated) {
|
||||
// If the given operationName isn't found, just make this whole transform a
|
||||
// no-op instead of crashing.
|
||||
return ast;
|
||||
}
|
||||
return separated;
|
||||
}
|
||||
|
||||
// Like lodash's sortBy, but sorted(undefined) === undefined rather than []. It
|
||||
// is a stable non-in-place sort.
|
||||
function sorted<T>(
|
||||
items: ReadonlyArray<T> | undefined,
|
||||
...iteratees: Array<ListIteratee<T>>
|
||||
): Array<T> | undefined {
|
||||
if (items) {
|
||||
return sortBy(items, ...iteratees);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// sortAST sorts most multi-child nodes alphabetically. Using this as part of
|
||||
// your signature calculation function may make it easier to tell the difference
|
||||
// between queries that are similar to each other, and if for some reason your
|
||||
// GraphQL client generates query strings with elements in nondeterministic
|
||||
// order, it can make sure the queries are treated as identical.
|
||||
export function sortAST(ast: DocumentNode): DocumentNode {
|
||||
return visit(ast, {
|
||||
OperationDefinition(
|
||||
node: OperationDefinitionNode,
|
||||
): OperationDefinitionNode {
|
||||
return {
|
||||
...node,
|
||||
variableDefinitions: sorted(
|
||||
node.variableDefinitions,
|
||||
'variable.name.value',
|
||||
),
|
||||
};
|
||||
},
|
||||
SelectionSet(node: SelectionSetNode): SelectionSetNode {
|
||||
return {
|
||||
...node,
|
||||
// Define an ordering for field names in a SelectionSet. Field first,
|
||||
// then FragmentSpread, then InlineFragment. By a lovely coincidence,
|
||||
// the order we want them to appear in is alphabetical by node.kind.
|
||||
// Use sortBy instead of sorted because 'selections' is not optional.
|
||||
selections: sortBy(node.selections, 'kind', 'name.value'),
|
||||
};
|
||||
},
|
||||
Field(node: FieldNode): FieldNode {
|
||||
return {
|
||||
...node,
|
||||
arguments: sorted(node.arguments, 'name.value'),
|
||||
};
|
||||
},
|
||||
FragmentSpread(node: FragmentSpreadNode): FragmentSpreadNode {
|
||||
return { ...node, directives: sorted(node.directives, 'name.value') };
|
||||
},
|
||||
InlineFragment(node: InlineFragmentNode): InlineFragmentNode {
|
||||
return { ...node, directives: sorted(node.directives, 'name.value') };
|
||||
},
|
||||
FragmentDefinition(node: FragmentDefinitionNode): FragmentDefinitionNode {
|
||||
return {
|
||||
...node,
|
||||
directives: sorted(node.directives, 'name.value'),
|
||||
variableDefinitions: sorted(
|
||||
node.variableDefinitions,
|
||||
'variable.name.value',
|
||||
),
|
||||
};
|
||||
},
|
||||
Directive(node: DirectiveNode): DirectiveNode {
|
||||
return { ...node, arguments: sorted(node.arguments, 'name.value') };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// removeAliases gets rid of GraphQL aliases, a feature by which you can tell a
|
||||
// server to return a field's data under a different name from the field
|
||||
// name. Maybe this is useful if somebody somewhere inserts random aliases into
|
||||
// their queries.
|
||||
export function removeAliases(ast: DocumentNode): DocumentNode {
|
||||
return visit(ast, {
|
||||
Field(node: FieldNode): FieldNode {
|
||||
return {
|
||||
...node,
|
||||
alias: undefined,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Like the graphql-js print function, but deleting whitespace wherever
|
||||
// feasible. Specifically, all whitespace (outside of string literals) is
|
||||
// reduced to at most one space, and even that space is removed anywhere except
|
||||
// for between two alphanumerics.
|
||||
export function printWithReducedWhitespace(ast: DocumentNode): string {
|
||||
// In a GraphQL AST (which notably does not contain comments), the only place
|
||||
// where meaningful whitespace (or double quotes) can exist is in
|
||||
// StringNodes. So to print with reduced whitespace, we:
|
||||
// - temporarily sanitize strings by replacing their contents with hex
|
||||
// - use the default GraphQL printer
|
||||
// - minimize the whitespace with a simple regexp replacement
|
||||
// - convert strings back to their actual value
|
||||
// We normalize all strings to non-block strings for simplicity.
|
||||
|
||||
const sanitizedAST = visit(ast, {
|
||||
StringValue(node: StringValueNode): StringValueNode {
|
||||
return {
|
||||
...node,
|
||||
value: Buffer.from(node.value, 'utf8').toString('hex'),
|
||||
block: false,
|
||||
};
|
||||
},
|
||||
});
|
||||
const withWhitespace = print(sanitizedAST);
|
||||
const minimizedButStillHex = withWhitespace
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/([^_a-zA-Z0-9]) /g, (_, c) => c)
|
||||
.replace(/ ([^_a-zA-Z0-9])/g, (_, c) => c);
|
||||
return minimizedButStillHex.replace(/"([a-f0-9]+)"/g, (_, hex) =>
|
||||
JSON.stringify(Buffer.from(hex, 'hex').toString('utf8')),
|
||||
);
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["**/__tests__", "**/__mocks__"],
|
||||
"references": []
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-azure-functions",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Azure Functions",
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "../../../../tsconfig.test.base",
|
||||
"include": ["**/*"],
|
||||
"references": [
|
||||
{ "path": "../../" }
|
||||
{ "path": "../../" },
|
||||
{ "path": "../../../apollo-server-integration-testsuite" },
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-cloud-functions",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Production-ready Node.js GraphQL server for Google Cloud Functions",
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-cloudflare",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.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.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Core engine for Apollo GraphQL server",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -24,7 +24,7 @@
|
|||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollographql/apollo-tools": "^0.3.0",
|
||||
"@apollographql/apollo-tools": "^0.3.3",
|
||||
"@apollographql/graphql-playground-html": "^1.6.6",
|
||||
"@types/ws": "^6.0.0",
|
||||
"apollo-cache-control": "file:../apollo-cache-control",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"graphql-tag": "^2.9.2",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.2",
|
||||
"lodash": "^4.17.10",
|
||||
"sha.js": "^2.4.11",
|
||||
"subscriptions-transport-ws": "^0.9.11",
|
||||
"ws": "^6.0.0"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
|
||||
import {
|
||||
makeExecutableSchema,
|
||||
addMockFunctionsToSchema,
|
||||
GraphQLParseOptions,
|
||||
} from 'graphql-tools';
|
||||
import { Server as HttpServer } from 'http';
|
||||
import {
|
||||
execute,
|
||||
|
@ -12,7 +16,6 @@ import {
|
|||
DocumentNode,
|
||||
} from 'graphql';
|
||||
import { GraphQLExtension } from 'graphql-extensions';
|
||||
import { EngineReportingAgent } from 'apollo-engine-reporting';
|
||||
import { InMemoryLRUCache } from 'apollo-server-caching';
|
||||
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
|
||||
import runtimeSupportsUploads from './utils/runtimeSupportsUploads';
|
||||
|
@ -103,7 +106,7 @@ export class ApolloServerBase {
|
|||
public requestOptions: Partial<GraphQLOptions<any>> = Object.create(null);
|
||||
|
||||
private context?: Context | ContextFunction;
|
||||
private engineReportingAgent?: EngineReportingAgent;
|
||||
private engineReportingAgent?: import('apollo-engine-reporting').EngineReportingAgent;
|
||||
private engineServiceId?: string;
|
||||
private extensions: Array<() => GraphQLExtension>;
|
||||
private schemaHash: string;
|
||||
|
@ -124,6 +127,8 @@ export class ApolloServerBase {
|
|||
// on the same operation to be executed immediately.
|
||||
private documentStore?: InMemoryLRUCache<DocumentNode>;
|
||||
|
||||
private parseOptions: GraphQLParseOptions;
|
||||
|
||||
// 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.');
|
||||
|
@ -134,6 +139,7 @@ export class ApolloServerBase {
|
|||
schemaDirectives,
|
||||
modules,
|
||||
typeDefs,
|
||||
parseOptions = {},
|
||||
introspection,
|
||||
mocks,
|
||||
mockEntireSchema,
|
||||
|
@ -292,9 +298,12 @@ export class ApolloServerBase {
|
|||
typeDefs: augmentedTypeDefs,
|
||||
schemaDirectives,
|
||||
resolvers,
|
||||
parseOptions,
|
||||
});
|
||||
}
|
||||
|
||||
this.parseOptions = parseOptions;
|
||||
|
||||
if (mocks || (typeof mockEntireSchema !== 'undefined' && mocks !== false)) {
|
||||
addMockFunctionsToSchema({
|
||||
schema: this.schema,
|
||||
|
@ -333,6 +342,7 @@ export class ApolloServerBase {
|
|||
this.engineServiceId = getEngineServiceId(engine);
|
||||
|
||||
if (this.engineServiceId) {
|
||||
const { EngineReportingAgent } = require('apollo-engine-reporting');
|
||||
this.engineReportingAgent = new EngineReportingAgent(
|
||||
typeof engine === 'object' ? engine : Object.create(null),
|
||||
{
|
||||
|
@ -546,6 +556,7 @@ export class ApolloServerBase {
|
|||
any,
|
||||
any
|
||||
>,
|
||||
parseOptions: this.parseOptions,
|
||||
...this.requestOptions,
|
||||
} as GraphQLOptions;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { CacheControlExtensionOptions } from 'apollo-cache-control';
|
|||
import { KeyValueCache, InMemoryLRUCache } from 'apollo-server-caching';
|
||||
import { DataSource } from 'apollo-datasource';
|
||||
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
|
||||
import { GraphQLParseOptions } from 'graphql-tools';
|
||||
|
||||
/*
|
||||
* GraphQLServerOptions
|
||||
|
@ -22,6 +23,7 @@ import { ApolloServerPlugin } from 'apollo-server-plugin-base';
|
|||
* - (optional) fieldResolver: a custom default field resolver
|
||||
* - (optional) debug: a boolean that will print additional debug logging if execution errors occur
|
||||
* - (optional) extensions: an array of functions which create GraphQLExtensions (each GraphQLExtension object is used for one request)
|
||||
* - (optional) parseOptions: options to pass when parsing schemas and queries
|
||||
*
|
||||
*/
|
||||
export interface GraphQLServerOptions<
|
||||
|
@ -44,6 +46,7 @@ export interface GraphQLServerOptions<
|
|||
persistedQueries?: PersistedQueryOptions;
|
||||
plugins?: ApolloServerPlugin[];
|
||||
documentStore?: InMemoryLRUCache<DocumentNode>;
|
||||
parseOptions?: GraphQLParseOptions;
|
||||
}
|
||||
|
||||
export type DataSources<TContext> = {
|
||||
|
|
|
@ -29,7 +29,6 @@ import {
|
|||
PersistedQueryNotSupportedError,
|
||||
PersistedQueryNotFoundError,
|
||||
} from 'apollo-server-errors';
|
||||
import { createHash } from 'crypto';
|
||||
import {
|
||||
GraphQLRequest,
|
||||
GraphQLResponse,
|
||||
|
@ -45,6 +44,7 @@ import {
|
|||
|
||||
import { Dispatcher } from './utils/dispatcher';
|
||||
import { InMemoryLRUCache, KeyValueCache } from 'apollo-server-caching';
|
||||
import { GraphQLParseOptions } from 'graphql-tools';
|
||||
|
||||
export {
|
||||
GraphQLRequest,
|
||||
|
@ -53,8 +53,10 @@ export {
|
|||
InvalidGraphQLRequestError,
|
||||
};
|
||||
|
||||
import createSHA from './utils/createSHA';
|
||||
|
||||
function computeQueryHash(query: string) {
|
||||
return createHash('sha256')
|
||||
return createSHA('sha256')
|
||||
.update(query)
|
||||
.digest('hex');
|
||||
}
|
||||
|
@ -78,6 +80,8 @@ export interface GraphQLRequestPipelineConfig<TContext> {
|
|||
|
||||
plugins?: ApolloServerPlugin[];
|
||||
documentStore?: InMemoryLRUCache<DocumentNode>;
|
||||
|
||||
parseOptions?: GraphQLParseOptions;
|
||||
}
|
||||
|
||||
export type DataSources<TContext> = {
|
||||
|
@ -194,7 +198,7 @@ export async function processGraphQLRequest<TContext>(
|
|||
);
|
||||
|
||||
try {
|
||||
requestContext.document = parse(query);
|
||||
requestContext.document = parse(query, config.parseOptions);
|
||||
parsingDidEnd();
|
||||
} catch (syntaxError) {
|
||||
parsingDidEnd(syntaxError);
|
||||
|
@ -306,13 +310,16 @@ export async function processGraphQLRequest<TContext>(
|
|||
requestDidEnd();
|
||||
}
|
||||
|
||||
function parse(query: string): DocumentNode {
|
||||
function parse(
|
||||
query: string,
|
||||
parseOptions?: GraphQLParseOptions,
|
||||
): DocumentNode {
|
||||
const parsingDidEnd = extensionStack.parsingDidStart({
|
||||
queryString: query,
|
||||
});
|
||||
|
||||
try {
|
||||
return graphql.parse(query);
|
||||
return graphql.parse(query, parseOptions);
|
||||
} finally {
|
||||
parsingDidEnd();
|
||||
}
|
||||
|
@ -356,7 +363,7 @@ export async function processGraphQLRequest<TContext>(
|
|||
});
|
||||
|
||||
try {
|
||||
return graphql.execute(executionArgs);
|
||||
return await graphql.execute(executionArgs);
|
||||
} finally {
|
||||
executionDidEnd();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { GraphQLSchema, DocumentNode } from 'graphql';
|
||||
import { SchemaDirectiveVisitor, IResolvers, IMocks } from 'graphql-tools';
|
||||
import {
|
||||
SchemaDirectiveVisitor,
|
||||
IResolvers,
|
||||
IMocks,
|
||||
GraphQLParseOptions,
|
||||
} from 'graphql-tools';
|
||||
import { ConnectionContext } from 'subscriptions-transport-ws';
|
||||
import WebSocket from 'ws';
|
||||
import { GraphQLExtension } from 'graphql-extensions';
|
||||
|
@ -42,23 +47,25 @@ export interface SubscriptionServerOptions {
|
|||
onDisconnect?: (websocket: WebSocket, context: ConnectionContext) => any;
|
||||
}
|
||||
|
||||
type BaseConfig = Pick<
|
||||
GraphQLOptions<Context<any>>,
|
||||
| 'formatError'
|
||||
| 'debug'
|
||||
| 'rootValue'
|
||||
| 'validationRules'
|
||||
| 'formatResponse'
|
||||
| 'fieldResolver'
|
||||
| 'tracing'
|
||||
| 'dataSources'
|
||||
| 'cache'
|
||||
>;
|
||||
|
||||
// This configuration is shared between all integrations and should include
|
||||
// fields that are not specific to a single integration
|
||||
export interface Config
|
||||
extends Pick<
|
||||
GraphQLOptions<Context<any>>,
|
||||
| 'formatError'
|
||||
| 'debug'
|
||||
| 'rootValue'
|
||||
| 'validationRules'
|
||||
| 'formatResponse'
|
||||
| 'fieldResolver'
|
||||
| 'tracing'
|
||||
| 'dataSources'
|
||||
| 'cache'
|
||||
> {
|
||||
export interface Config extends BaseConfig {
|
||||
modules?: GraphQLSchemaModule[];
|
||||
typeDefs?: DocumentNode | Array<DocumentNode>;
|
||||
parseOptions?: GraphQLParseOptions;
|
||||
resolvers?: IResolvers;
|
||||
schema?: GraphQLSchema;
|
||||
schemaDirectives?: Record<string, typeof SchemaDirectiveVisitor>;
|
||||
|
|
10
packages/apollo-server-core/src/utils/createSHA.ts
Normal file
10
packages/apollo-server-core/src/utils/createSHA.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import isNode from './isNode';
|
||||
|
||||
export default function(kind: string): import('crypto').Hash {
|
||||
if (isNode) {
|
||||
// Use module.require instead of just require to avoid bundling whatever
|
||||
// crypto polyfills a non-Node bundler might fall back to.
|
||||
return module.require('crypto').createHash(kind);
|
||||
}
|
||||
return require('sha.js')(kind);
|
||||
}
|
6
packages/apollo-server-core/src/utils/isNode.ts
Normal file
6
packages/apollo-server-core/src/utils/isNode.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default typeof process === 'object' &&
|
||||
process &&
|
||||
process.release &&
|
||||
process.release.name === 'node' &&
|
||||
process.versions &&
|
||||
typeof process.versions.node === 'string';
|
|
@ -1,11 +1,7 @@
|
|||
import isNode from './isNode';
|
||||
|
||||
const runtimeSupportsUploads = (() => {
|
||||
if (
|
||||
process &&
|
||||
process.release &&
|
||||
process.release.name === 'node' &&
|
||||
process.versions &&
|
||||
typeof process.versions.node === 'string'
|
||||
) {
|
||||
if (isNode) {
|
||||
const [nodeMajor, nodeMinor] = process.versions.node
|
||||
.split('.', 2)
|
||||
.map(segment => parseInt(segment, 10));
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
import { parse } from 'graphql/language';
|
||||
import { execute, ExecutionResult } from 'graphql/execution';
|
||||
import { getIntrospectionQuery, IntrospectionSchema } from 'graphql/utilities';
|
||||
import stableStringify from 'fast-json-stable-stringify';
|
||||
import { GraphQLSchema } from 'graphql/type';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
export function generateSchemaHash(schema: GraphQLSchema): string {
|
||||
const introspectionQuery = getIntrospectionQuery();
|
||||
const documentAST = parse(introspectionQuery);
|
||||
const result = execute(schema, documentAST) as ExecutionResult;
|
||||
|
||||
// If the execution of an introspection query results in a then-able, it
|
||||
// indicates that one or more of its resolvers is behaving in an asynchronous
|
||||
// manner. This is not the expected behavior of a introspection query
|
||||
// which does not have any asynchronous resolvers.
|
||||
if (
|
||||
result &&
|
||||
typeof (result as PromiseLike<typeof result>).then === 'function'
|
||||
) {
|
||||
throw new Error(
|
||||
[
|
||||
'The introspection query is resolving asynchronously; execution of an introspection query is not expected to return a `Promise`.',
|
||||
'',
|
||||
'Wrapped type resolvers should maintain the existing execution dynamics of the resolvers they wrap (i.e. async vs sync) or introspection types should be excluded from wrapping by checking them with `graphql/type`s, `isIntrospectionType` predicate function prior to wrapping.',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
if (!result || !result.data || !result.data.__schema) {
|
||||
throw new Error('Unable to generate server introspection document.');
|
||||
}
|
||||
|
||||
const introspectionSchema: IntrospectionSchema = result.data.__schema;
|
||||
|
||||
// It's important that we perform a deterministic stringification here
|
||||
// since, depending on changes in the underlying `graphql-js` execution
|
||||
// layer, varying orders of the properties in the introspection
|
||||
const stringifiedSchema = stableStringify(introspectionSchema);
|
||||
|
||||
return createHash('sha512')
|
||||
.update(stringifiedSchema)
|
||||
.digest('hex');
|
||||
}
|
||||
import { parse } from 'graphql/language';
|
||||
import { execute, ExecutionResult } from 'graphql/execution';
|
||||
import { getIntrospectionQuery, IntrospectionSchema } from 'graphql/utilities';
|
||||
import stableStringify from 'fast-json-stable-stringify';
|
||||
import { GraphQLSchema } from 'graphql/type';
|
||||
import createSHA from './createSHA';
|
||||
|
||||
export function generateSchemaHash(schema: GraphQLSchema): string {
|
||||
const introspectionQuery = getIntrospectionQuery();
|
||||
const documentAST = parse(introspectionQuery);
|
||||
const result = execute(schema, documentAST) as ExecutionResult;
|
||||
|
||||
// If the execution of an introspection query results in a then-able, it
|
||||
// indicates that one or more of its resolvers is behaving in an asynchronous
|
||||
// manner. This is not the expected behavior of a introspection query
|
||||
// which does not have any asynchronous resolvers.
|
||||
if (
|
||||
result &&
|
||||
typeof (result as PromiseLike<typeof result>).then === 'function'
|
||||
) {
|
||||
throw new Error(
|
||||
[
|
||||
'The introspection query is resolving asynchronously; execution of an introspection query is not expected to return a `Promise`.',
|
||||
'',
|
||||
'Wrapped type resolvers should maintain the existing execution dynamics of the resolvers they wrap (i.e. async vs sync) or introspection types should be excluded from wrapping by checking them with `graphql/type`s, `isIntrospectionType` predicate function prior to wrapping.',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
if (!result || !result.data || !result.data.__schema) {
|
||||
throw new Error('Unable to generate server introspection document.');
|
||||
}
|
||||
|
||||
const introspectionSchema: IntrospectionSchema = result.data.__schema;
|
||||
|
||||
// It's important that we perform a deterministic stringification here
|
||||
// since, depending on changes in the underlying `graphql-js` execution
|
||||
// layer, varying orders of the properties in the introspection
|
||||
const stringifiedSchema = stableStringify(introspectionSchema);
|
||||
|
||||
return createSHA('sha512')
|
||||
.update(stringifiedSchema)
|
||||
.digest('hex');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-express",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.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.4.0",
|
||||
"version": "2.4.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.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Apollo Server Integrations testsuite",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
ApolloServerBase,
|
||||
} from 'apollo-server-core';
|
||||
import { GraphQLExtension, GraphQLResponse } from 'graphql-extensions';
|
||||
import { TracingFormat } from 'apollo-tracing';
|
||||
|
||||
export function createServerInfo<AS extends ApolloServerBase>(
|
||||
server: AS,
|
||||
|
@ -1260,5 +1261,67 @@ export function testApolloServer<AS extends ApolloServerBase>(
|
|||
}, done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tracing', () => {
|
||||
const typeDefs = gql`
|
||||
type Book {
|
||||
title: String
|
||||
author: String
|
||||
}
|
||||
|
||||
type Movie {
|
||||
title: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
books: [Book]
|
||||
movies: [Movie]
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
books: () =>
|
||||
new Promise(resolve =>
|
||||
setTimeout(() => resolve([{ title: 'H', author: 'J' }]), 10),
|
||||
),
|
||||
movies: () =>
|
||||
new Promise(resolve =>
|
||||
setTimeout(() => resolve([{ title: 'H' }]), 12),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
it('reports a total duration that is longer than the duration of its resolvers', async () => {
|
||||
const { url: uri } = await createApolloServer({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
tracing: true,
|
||||
});
|
||||
|
||||
const apolloFetch = createApolloFetch({ uri });
|
||||
const result = await apolloFetch({
|
||||
query: `{ books { title author } }`,
|
||||
});
|
||||
|
||||
const tracing: TracingFormat = result.extensions.tracing;
|
||||
|
||||
const earliestStartOffset = tracing.execution.resolvers
|
||||
.map(resolver => resolver.startOffset)
|
||||
.reduce((currentEarliestOffset, nextOffset) =>
|
||||
Math.min(currentEarliestOffset, nextOffset),
|
||||
);
|
||||
|
||||
const latestEndOffset = tracing.execution.resolvers
|
||||
.map(resolver => resolver.startOffset + resolver.duration)
|
||||
.reduce((currentLatestEndOffset, nextEndOffset) =>
|
||||
Math.min(currentLatestEndOffset, nextEndOffset),
|
||||
);
|
||||
|
||||
const resolverDuration = latestEndOffset - earliestStartOffset;
|
||||
|
||||
expect(resolverDuration).not.toBeGreaterThan(tracing.duration);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-koa",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.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.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Production-ready Node.js GraphQL server for AWS Lambda",
|
||||
"keywords": [
|
||||
"GraphQL",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "apollo-server-micro",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.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.3.0",
|
||||
"version": "0.3.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.4.0",
|
||||
"version": "2.4.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.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Production ready GraphQL Server",
|
||||
"author": "opensource@apollographql.com",
|
||||
"main": "dist/index.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "graphql-extensions",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"description": "Add extensions to GraphQL servers",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
@ -14,7 +14,7 @@
|
|||
"node": ">=6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollographql/apollo-tools": "^0.3.0"
|
||||
"@apollographql/apollo-tools": "^0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apollo-server-core": "file:../apollo-server-core",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"extends": [
|
||||
"apollo-open-source"
|
||||
],
|
||||
"automerge": false,
|
||||
"packageRules": [
|
||||
{
|
||||
"paths": [
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
{ "path": "./packages/apollo-datasource" },
|
||||
{ "path": "./packages/apollo-datasource-rest" },
|
||||
{ "path": "./packages/apollo-engine-reporting" },
|
||||
{ "path": "./packages/apollo-graphql" },
|
||||
{ "path": "./packages/apollo-server" },
|
||||
{ "path": "./packages/apollo-server-azure-functions" },
|
||||
{ "path": "./packages/apollo-server-cache-memcached" },
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
{ "path": "./packages/apollo-cache-control/src/__tests__/" },
|
||||
{ "path": "./packages/apollo-datasource-rest/src/__tests__/" },
|
||||
{ "path": "./packages/apollo-engine-reporting/src/__tests__/" },
|
||||
{ "path": "./packages/apollo-graphql/src/__tests__/" },
|
||||
{ "path": "./packages/apollo-server/src/__tests__/" },
|
||||
{ "path": "./packages/apollo-server-azure-functions/src/__tests__/" },
|
||||
{ "path": "./packages/apollo-server-cache-memcached/src/__tests__/" },
|
||||
|
|
Loading…
Add table
Reference in a new issue