diff --git a/.changeset/wet-berries-report.md b/.changeset/wet-berries-report.md new file mode 100644 index 00000000000..95c58203bc5 --- /dev/null +++ b/.changeset/wet-berries-report.md @@ -0,0 +1,5 @@ +--- +'@apollo/server': patch +--- + +Previously, when users provided their own `documentStore`, Apollo Server used a random prefix per schema in order to guarantee there was no shared state from one schema to the next. Now Apollo Server uses a hash of the schema, which enables the provided document store to be shared if you choose to do so. diff --git a/packages/server/src/ApolloServer.ts b/packages/server/src/ApolloServer.ts index 68a5ac369ea..75086d959f7 100644 --- a/packages/server/src/ApolloServer.ts +++ b/packages/server/src/ApolloServer.ts @@ -11,6 +11,7 @@ import { type GraphQLSchema, type ParseOptions, print, + printSchema, type TypedQueryDocumentNode, type ValidationContext, type ValidationRule, @@ -22,7 +23,6 @@ import { } from '@apollo/utils.keyvaluecache'; import loglevel from 'loglevel'; import Negotiator from 'negotiator'; -import * as uuid from 'uuid'; import { newCachePolicy } from './cachePolicy.js'; import { determineApolloConfig } from './determineApolloConfig.js'; import { @@ -63,6 +63,7 @@ import { newHTTPGraphQLHead, prettyJSONStringify } from './runHttpQuery.js'; import { SchemaManager } from './utils/schemaManager.js'; import { isDefined } from './utils/isDefined.js'; import { UnreachableCaseError } from './utils/UnreachableCaseError.js'; +import { computeCoreSchemaHash } from './utils/computeCoreSchemaHash.js'; import type { WithRequired } from '@apollo/utils.withrequired'; import type { ApolloServerOptionsWithStaticSchema } from './externalTypes/constructor.js'; import type { GatewayExecutor } from '@apollo/server-gateway-interface'; @@ -728,13 +729,15 @@ export class ApolloServer { // same DocumentStore for different schemas because that might make us // treat invalid operations as valid. If we're using the default // DocumentStore, then we just create it from scratch each time we get a - // new schema. If we're using a user-provided DocumentStore, then we use a - // random prefix each time we get a new schema. + // new schema. If we're using a user-provided DocumentStore, then we use + // the schema hash as a prefix. documentStore: providedDocumentStore === undefined ? new InMemoryLRUCache() : providedDocumentStore, - documentStoreKeyPrefix: providedDocumentStore ? `${uuid.v4()}:` : '', + documentStoreKeyPrefix: providedDocumentStore + ? `${computeCoreSchemaHash(printSchema(schema))}:` + : '', }; } diff --git a/packages/server/src/__tests__/documentStore.test.ts b/packages/server/src/__tests__/documentStore.test.ts index 9f78158c5a6..f9c7cc9eedf 100644 --- a/packages/server/src/__tests__/documentStore.test.ts +++ b/packages/server/src/__tests__/documentStore.test.ts @@ -4,6 +4,9 @@ import gql from 'graphql-tag'; import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '..'; import { jest, describe, it, expect } from '@jest/globals'; +import { makeExecutableSchema } from '@graphql-tools/schema'; +import { printSchema } from 'graphql/index'; +import { computeCoreSchemaHash } from '../utils/computeCoreSchemaHash'; const typeDefs = gql` type Query { @@ -81,11 +84,16 @@ describe('ApolloServer documentStore', () => { const keys = documentStore.keys(); expect(keys).toHaveLength(1); const theKey = keys[0]; - const [uuid, hash] = theKey.split(':'); - expect(typeof uuid).toBe('string'); - expect(hash).toEqual(operations.simple.hash); - const result = await documentStore.get(`${uuid}:${hash}`); + const schema = makeExecutableSchema({ typeDefs, resolvers }); + const expectedSchemaHash = computeCoreSchemaHash(printSchema(schema)); + + const [schemaHash, documentHash] = theKey.split(':'); + expect(typeof schemaHash).toBe('string'); + expect(schemaHash).toEqual(expectedSchemaHash); + expect(documentHash).toEqual(operations.simple.hash); + + const result = await documentStore.get(`${schemaHash}:${documentHash}`); expect(result).toMatchObject(documentNodeMatcher); await server.executeOperation(operations.simple.op);