From 2360ab17d225db654daa06d395d643ab8f887f1f Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Wed, 22 May 2019 18:42:27 +0300 Subject: [PATCH] Defer and cache calculation of Engine reporting signature. (#2670) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Defer and cache calculation of Engine reporting signature. This moves the Apollo Engine Reporting signature calculation (which the Apollo Cloud uses to aggregate similar operations) to perform asynchronously, after the response has been returned to the client, and also caches the signature for use by additional operations which match the same `queryHash` (which is an exact SHA-256 hex digest of the operation body received from the client). While the signature calculation is relatively quick on small operations, with sustained load and more complex operations, this recurring calculation can be more costly. As a bit of validation to the success of this change, on a very basic performance benchmark, using a schema with 1000 `String` fields (i.e. a `type Query` with `Field_1: String` through `Field_1000: String`) and an incoming operation which selects from all 1000 of those fields (i.e `Field_1`), this showed quite an improvement: ``` ┌───────────────┬───────────────┬───────────────┐ │ │ Before │ After │ ├───────────────┼───────────────┼───────────────┤ │ Percentile, % │ Resp. Time, s │ Resp. Time, s │ ├───────────────┼───────────────┼───────────────┤ │ 0.0 │ 0.076 │ 0.024 │ │ 50.0 │ 0.342 │ 0.14 │ │ 90.0 │ 0.388 │ 0.161 │ │ 95.0 │ 0.41 │ 0.164 │ │ 99.0 │ 0.444 │ 0.17 │ │ 99.9 │ 0.486 │ 0.177 │ │ 100.0 │ 0.487 │ 0.196 │ └───────────────┴───────────────┴───────────────┘ ``` Of course, this is a relatively simple example and still includes actual GraphQL execution, but the win factor is certainly there. Other tests with more dynamic fields and a higher cardinality of unique operations also showed improvement, though extremely high cardinality of complex operations (which have more expensive execution characteristics) certainly made the _win_ factor less pronounced. It's not surprising that this calculation is a bit expensive since it requires a number of normalization steps. Luckily, we can let this all happen after the request is sent to the client and it's highly cacheable. By default, we'll use a relatively small cache which should store a large number of operations, and to avoid introducing configuration surface area right now without much evidence as to the need, we'll simply log a message periodically if we're ejecting operations. * Publish - apollo-cache-control@0.7.0-alpha.0 - apollo-engine-reporting@1.2.0-alpha.0 - apollo-server-azure-functions@2.6.0-alpha.0 - apollo-server-cloud-functions@2.6.0-alpha.0 - apollo-server-cloudflare@2.6.0-alpha.0 - apollo-server-core@2.6.0-alpha.0 - apollo-server-express@2.6.0-alpha.0 - apollo-server-fastify@2.6.0-alpha.0 - apollo-server-hapi@2.6.0-alpha.0 - apollo-server-integration-testsuite@2.6.0-alpha.0 - apollo-server-koa@2.6.0-alpha.0 - apollo-server-lambda@2.6.0-alpha.0 - apollo-server-micro@2.6.0-alpha.0 - apollo-server-plugin-base@0.5.0-alpha.0 - apollo-server-plugin-response-cache@0.2.0-alpha.0 - apollo-server-testing@2.6.0-alpha.0 - apollo-server@2.6.0-alpha.0 - apollo-tracing@0.7.0-alpha.0 - graphql-extensions@0.7.0-alpha.0 * Use `operationName` as part of cache key for Engine reporting signature. Per astute feedback from @martijnwalraven! --- packages/apollo-cache-control/package.json | 2 +- packages/apollo-engine-reporting/package.json | 2 +- .../src/__tests__/agent.test.ts | 13 ++ .../src/__tests__/extension.test.ts | 6 +- packages/apollo-engine-reporting/src/agent.ts | 131 +++++++++++++++++- .../apollo-engine-reporting/src/extension.ts | 47 +++---- .../package.json | 2 +- .../package.json | 2 +- .../apollo-server-cloudflare/package.json | 2 +- packages/apollo-server-core/package.json | 2 +- .../apollo-server-core/src/requestPipeline.ts | 5 +- packages/apollo-server-express/package.json | 2 +- packages/apollo-server-fastify/package.json | 2 +- packages/apollo-server-hapi/package.json | 2 +- .../package.json | 2 +- packages/apollo-server-koa/package.json | 2 +- packages/apollo-server-lambda/package.json | 2 +- packages/apollo-server-micro/package.json | 2 +- .../apollo-server-plugin-base/package.json | 2 +- .../package.json | 2 +- packages/apollo-server-testing/package.json | 2 +- packages/apollo-server/package.json | 2 +- packages/apollo-tracing/package.json | 2 +- packages/graphql-extensions/package.json | 2 +- 24 files changed, 189 insertions(+), 51 deletions(-) create mode 100644 packages/apollo-engine-reporting/src/__tests__/agent.test.ts diff --git a/packages/apollo-cache-control/package.json b/packages/apollo-cache-control/package.json index cb1714479ef..8282fb94755 100644 --- a/packages/apollo-cache-control/package.json +++ b/packages/apollo-cache-control/package.json @@ -1,6 +1,6 @@ { "name": "apollo-cache-control", - "version": "0.6.0", + "version": "0.7.0-alpha.0", "description": "A GraphQL extension for cache control", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/apollo-engine-reporting/package.json b/packages/apollo-engine-reporting/package.json index a1efcd64f90..0abbd74780a 100644 --- a/packages/apollo-engine-reporting/package.json +++ b/packages/apollo-engine-reporting/package.json @@ -1,6 +1,6 @@ { "name": "apollo-engine-reporting", - "version": "1.1.0", + "version": "1.2.0-alpha.0", "description": "Send reports about your GraphQL services to Apollo Engine", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/apollo-engine-reporting/src/__tests__/agent.test.ts b/packages/apollo-engine-reporting/src/__tests__/agent.test.ts new file mode 100644 index 00000000000..3a6c76ced79 --- /dev/null +++ b/packages/apollo-engine-reporting/src/__tests__/agent.test.ts @@ -0,0 +1,13 @@ +import { signatureCacheKey } from '../agent'; + +describe('signature cache key', () => { + it('generates without the operationName', () => { + expect(signatureCacheKey('abc123', '')).toEqual('abc123'); + }); + + it('generates without the operationName', () => { + expect(signatureCacheKey('abc123', 'myOperation')).toEqual( + 'abc123:myOperation', + ); + }); +}); diff --git a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts index 841854b856f..c61eccbfd75 100644 --- a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts +++ b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts @@ -50,7 +50,11 @@ test('trace construction', async () => { enableGraphQLExtensions(schema); const traces: Array = []; - function addTrace(signature: string, operationName: string, trace: Trace) { + function addTrace( + signature: Promise, + operationName: string, + trace: Trace, + ) { traces.push({ signature, operationName, trace }); } diff --git a/packages/apollo-engine-reporting/src/agent.ts b/packages/apollo-engine-reporting/src/agent.ts index fd77b6ce831..2535a42a4a8 100644 --- a/packages/apollo-engine-reporting/src/agent.ts +++ b/packages/apollo-engine-reporting/src/agent.ts @@ -16,6 +16,8 @@ import { GraphQLRequestContext, GraphQLServiceContext, } from 'apollo-server-core/dist/requestPipelineAPI'; +import { InMemoryLRUCache } from 'apollo-server-caching'; +import { defaultEngineReportingSignature } from 'apollo-graphql'; export interface ClientInfo { clientName?: string; @@ -128,6 +130,14 @@ export interface EngineReportingOptions { generateClientInfo?: GenerateClientInfo; } +export interface AddTraceArgs { + trace: Trace; + operationName: string; + queryHash: string; + queryString?: string; + documentAST?: DocumentNode; +} + const serviceHeaderDefaults = { hostname: os.hostname(), // tslint:disable-next-line no-var-requires @@ -149,6 +159,7 @@ export class EngineReportingAgent { private sendReportsImmediately?: boolean; private stopped: boolean = false; private reportHeader: ReportHeader; + private signatureCache: InMemoryLRUCache; public constructor( options: EngineReportingOptions = {}, @@ -162,6 +173,11 @@ export class EngineReportingAgent { ); } + // Since calculating the signature for Engine reporting is potentially an + // expensive operation, we'll cache the signatures we generate and re-use + // them based on repeated traces for the same `queryHash`. + this.signatureCache = createSignatureCache(); + this.reportHeader = new ReportHeader({ ...serviceHeaderDefaults, schemaHash, @@ -196,7 +212,61 @@ export class EngineReportingAgent { ); } - public addTrace(signature: string, operationName: string, trace: Trace) { + private async getTraceSignature({ + queryHash, + operationName, + documentAST, + queryString, + }: { + queryHash: string; + operationName: string; + documentAST?: DocumentNode; + queryString?: string; + }): Promise { + if (!documentAST && !queryString) { + // This shouldn't happen: one of those options must be passed to runQuery. + throw new Error('No queryString or parsedQuery?'); + } + + const cacheKey = signatureCacheKey(queryHash, operationName); + + // If we didn't have the signature in the cache, we'll resort to + // calculating it asynchronously. The `addTrace` method will + // `await` the `signature` if it's a Promise, prior to putting it + // on the stack of traces to deliver to the cloud. + const cachedSignature = await this.signatureCache.get(cacheKey); + + if (cachedSignature) { + return cachedSignature; + } + + if (!documentAST) { + // We didn't get an AST, possibly because of a parse failure. Let's just + // use the full query string. + // + // XXX This does mean that even if you use a calculateSignature which + // hides literals, you might end up sending literals for queries + // that fail parsing or validation. Provide some way to mask them + // anyway? + return queryString as string; + } + + const generatedSignature = (this.options.calculateSignature || + defaultEngineReportingSignature)(documentAST, operationName); + + // Intentionally not awaited so the cache can be written to at leisure. + this.signatureCache.set(cacheKey, generatedSignature); + + return generatedSignature; + } + + public async addTrace({ + trace, + queryHash, + documentAST, + operationName, + queryString, + }: AddTraceArgs): Promise { // Ignore traces that come in after stop(). if (this.stopped) { return; @@ -208,6 +278,13 @@ export class EngineReportingAgent { } const encodedTrace = Trace.encode(trace).finish(); + const signature = await this.getTraceSignature({ + queryHash, + documentAST, + queryString, + operationName, + }); + const statsReportKey = `# ${operationName || '-'}\n${signature}`; if (!this.report.tracesPerQuery.hasOwnProperty(statsReportKey)) { this.report.tracesPerQuery[statsReportKey] = new Traces(); @@ -226,7 +303,7 @@ export class EngineReportingAgent { this.reportSize >= (this.options.maxUncompressedReportSize || 4 * 1024 * 1024) ) { - this.sendReportAndReportErrors(); + await this.sendReportAndReportErrors(); } } @@ -351,3 +428,53 @@ export class EngineReportingAgent { this.reportSize = 0; } } + +function createSignatureCache(): InMemoryLRUCache { + let lastSignatureCacheWarn: Date; + let lastSignatureCacheDisposals: number = 0; + return new InMemoryLRUCache({ + // Calculate the length of cache objects by the JSON.stringify byteLength. + sizeCalculator(obj) { + return Buffer.byteLength(JSON.stringify(obj), 'utf8'); + }, + // 3MiB limit, very much approximately since we can't be sure how V8 might + // be storing these strings internally. Though this should be enough to + // store a fair amount of operation signatures (~10000?), depending on their + // overall complexity. A future version of this might expose some + // configuration option to grow the cache, but ideally, we could do that + // dynamically based on the resources available to the server, and not add + // more configuration surface area. Hopefully the warning message will allow + // us to evaluate the need with more validated input from those that receive + // it. + maxSize: Math.pow(2, 20) * 3, + onDispose() { + // Count the number of disposals between warning messages. + lastSignatureCacheDisposals++; + + // Only show a message warning about the high turnover every 60 seconds. + if ( + !lastSignatureCacheWarn || + new Date().getTime() - lastSignatureCacheWarn.getTime() > 60000 + ) { + // Log the time that we last displayed the message. + lastSignatureCacheWarn = new Date(); + console.warn( + [ + 'This server is processing a high number of unique operations. ', + `A total of ${lastSignatureCacheDisposals} records have been `, + 'ejected from the Engine Reporting signature cache in the past ', + 'interval. If you see this warning frequently, please open an ', + 'issue on the Apollo Server repository.', + ].join(''), + ); + + // Reset the disposal counter for the next message interval. + lastSignatureCacheDisposals = 0; + } + }, + }); +} + +export function signatureCacheKey(queryHash: string, operationName: string) { + return `${queryHash}${operationName && ':' + operationName}`; +} diff --git a/packages/apollo-engine-reporting/src/extension.ts b/packages/apollo-engine-reporting/src/extension.ts index 7bd2c724c8f..1b2ae4dc996 100644 --- a/packages/apollo-engine-reporting/src/extension.ts +++ b/packages/apollo-engine-reporting/src/extension.ts @@ -11,8 +11,11 @@ import { import { GraphQLExtension, EndHandler } from 'graphql-extensions'; import { Trace, google } from 'apollo-engine-reporting-protobuf'; -import { EngineReportingOptions, GenerateClientInfo } from './agent'; -import { defaultEngineReportingSignature } from 'apollo-graphql'; +import { + EngineReportingOptions, + GenerateClientInfo, + AddTraceArgs, +} from './agent'; import { GraphQLRequestContext } from 'apollo-server-core/dist/requestPipelineAPI'; const clientNameHeaderKey = 'apollographql-client-name'; @@ -47,16 +50,12 @@ export class EngineReportingExtension private queryString?: string; private documentAST?: DocumentNode; private options: EngineReportingOptions; - private addTrace: ( - signature: string, - operationName: string, - trace: Trace, - ) => void; + private addTrace: (args: AddTraceArgs) => Promise; private generateClientInfo: GenerateClientInfo; public constructor( options: EngineReportingOptions, - addTrace: (signature: string, operationName: string, trace: Trace) => void, + addTrace: (args: AddTraceArgs) => Promise, ) { this.options = { ...options, @@ -76,7 +75,10 @@ export class EngineReportingExtension variables?: Record; context: TContext; extensions?: Record; - requestContext: WithRequired, 'metrics'>; + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'queryHash' + >; }): EndHandler { this.trace.startTime = dateToTimestamp(new Date()); this.startHrTime = process.hrtime(); @@ -84,6 +86,7 @@ export class EngineReportingExtension // Generally, we'll get queryString here and not parsedQuery; we only get // parsedQuery if you're using an OperationStore. In normal cases we'll get // our documentAST in the execution callback after it is parsed. + const queryHash = o.requestContext.queryHash; this.queryString = o.queryString; this.documentAST = o.parsedQuery; @@ -197,26 +200,14 @@ export class EngineReportingExtension .responseCacheHit; const operationName = this.operationName || ''; - let signature; - if (this.documentAST) { - const calculateSignature = - this.options.calculateSignature || defaultEngineReportingSignature; - signature = calculateSignature(this.documentAST, operationName); - } else if (this.queryString) { - // We didn't get an AST, possibly because of a parse failure. Let's just - // use the full query string. - // - // XXX This does mean that even if you use a calculateSignature which - // hides literals, you might end up sending literals for queries - // that fail parsing or validation. Provide some way to mask them - // anyway? - signature = this.queryString; - } else { - // This shouldn't happen: one of those options must be passed to runQuery. - throw new Error('No queryString or parsedQuery?'); - } - this.addTrace(signature, operationName, this.trace); + this.addTrace({ + operationName, + queryHash, + documentAST: this.documentAST, + queryString: this.queryString || '', + trace: this.trace, + }); }; } diff --git a/packages/apollo-server-azure-functions/package.json b/packages/apollo-server-azure-functions/package.json index ad0bcc49659..a89bb5d89a9 100644 --- a/packages/apollo-server-azure-functions/package.json +++ b/packages/apollo-server-azure-functions/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-azure-functions", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Azure Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloud-functions/package.json b/packages/apollo-server-cloud-functions/package.json index 3cca278cd1d..301945f3cbe 100644 --- a/packages/apollo-server-cloud-functions/package.json +++ b/packages/apollo-server-cloud-functions/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-cloud-functions", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Google Cloud Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloudflare/package.json b/packages/apollo-server-cloudflare/package.json index 83928ef32e7..045821e0627 100644 --- a/packages/apollo-server-cloudflare/package.json +++ b/packages/apollo-server-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-cloudflare", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Cloudflare workers", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json index aee779db9b4..1bc4e18867e 100644 --- a/packages/apollo-server-core/package.json +++ b/packages/apollo-server-core/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-core", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Core engine for Apollo GraphQL server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 135dd174890..b5827492246 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -197,7 +197,10 @@ export async function processGraphQLRequest( context: requestContext.context, persistedQueryHit: metrics.persistedQueryHit, persistedQueryRegister: metrics.persistedQueryRegister, - requestContext, + requestContext: requestContext as WithRequired< + typeof requestContext, + 'metrics' | 'queryHash' + >, }); try { diff --git a/packages/apollo-server-express/package.json b/packages/apollo-server-express/package.json index da8fb6abc0f..31207d6f901 100644 --- a/packages/apollo-server-express/package.json +++ b/packages/apollo-server-express/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-express", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Express and Connect", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json index 26aab39915d..534ef3241b3 100644 --- a/packages/apollo-server-fastify/package.json +++ b/packages/apollo-server-fastify/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-fastify", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Fastify", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-hapi/package.json b/packages/apollo-server-hapi/package.json index 7daa1c2b98a..4aff0257b5e 100644 --- a/packages/apollo-server-hapi/package.json +++ b/packages/apollo-server-hapi/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-hapi", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Hapi", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-integration-testsuite/package.json b/packages/apollo-server-integration-testsuite/package.json index af4cd850ede..ed9bf1b2f2c 100644 --- a/packages/apollo-server-integration-testsuite/package.json +++ b/packages/apollo-server-integration-testsuite/package.json @@ -1,7 +1,7 @@ { "name": "apollo-server-integration-testsuite", "private": true, - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Apollo Server Integrations testsuite", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index 8322d3f5018..ea24a8ce22b 100644 --- a/packages/apollo-server-koa/package.json +++ b/packages/apollo-server-koa/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-koa", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Koa", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-lambda/package.json b/packages/apollo-server-lambda/package.json index 794e667d740..b2bf6dbd416 100644 --- a/packages/apollo-server-lambda/package.json +++ b/packages/apollo-server-lambda/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-lambda", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for AWS Lambda", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-micro/package.json b/packages/apollo-server-micro/package.json index a075909856f..6ee57509c28 100644 --- a/packages/apollo-server-micro/package.json +++ b/packages/apollo-server-micro/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-micro", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production-ready Node.js GraphQL server for Micro", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-plugin-base/package.json b/packages/apollo-server-plugin-base/package.json index 8c0781cf1a5..9232ec894e3 100644 --- a/packages/apollo-server-plugin-base/package.json +++ b/packages/apollo-server-plugin-base/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-plugin-base", - "version": "0.4.0", + "version": "0.5.0-alpha.0", "description": "Apollo Server plugin base classes", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-plugin-response-cache/package.json b/packages/apollo-server-plugin-response-cache/package.json index a76bdd6da43..c8174595b36 100644 --- a/packages/apollo-server-plugin-response-cache/package.json +++ b/packages/apollo-server-plugin-response-cache/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-plugin-response-cache", - "version": "0.1.0", + "version": "0.2.0-alpha.0", "description": "Apollo Server full query response cache", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-testing/package.json b/packages/apollo-server-testing/package.json index ba19058cf4d..1452ac6ef6e 100644 --- a/packages/apollo-server-testing/package.json +++ b/packages/apollo-server-testing/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-testing", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Test utils for apollo-server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server/package.json b/packages/apollo-server/package.json index 37e0c817fb1..744560dbaf2 100644 --- a/packages/apollo-server/package.json +++ b/packages/apollo-server/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server", - "version": "2.5.0", + "version": "2.6.0-alpha.0", "description": "Production ready GraphQL Server", "author": "opensource@apollographql.com", "main": "dist/index.js", diff --git a/packages/apollo-tracing/package.json b/packages/apollo-tracing/package.json index 15d207d31a8..57ce6b9516d 100644 --- a/packages/apollo-tracing/package.json +++ b/packages/apollo-tracing/package.json @@ -1,6 +1,6 @@ { "name": "apollo-tracing", - "version": "0.6.0", + "version": "0.7.0-alpha.0", "description": "Collect and expose trace data for GraphQL requests", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/graphql-extensions/package.json b/packages/graphql-extensions/package.json index 23bb8f05fc3..4effe8a1ee3 100644 --- a/packages/graphql-extensions/package.json +++ b/packages/graphql-extensions/package.json @@ -1,6 +1,6 @@ { "name": "graphql-extensions", - "version": "0.6.0", + "version": "0.7.0-alpha.0", "description": "Add extensions to GraphQL servers", "main": "./dist/index.js", "types": "./dist/index.d.ts",