diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f6181e46f..0d2ba15bca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The version headers in this history reflect the versions of Apollo Server itself - Removed deprecated `ApolloServer.schema` field, which never worked with gateways. If you'd like to extract your schema from your server, make a plugin with `serverWillStart`, or register `onSchemaChange` on your gateway. - `apollo-server-caching`: The test suite helper works differently, and the `TestableKeyValueCache` interface is removed. - `apollo-datasource-rest`: We no longer officially support overriding the `baseURL` property with a getter, because TypeScript 4 does not allow you to do that. +- Previously, Apollo Server would automatically add the definition of the `@cacheControl` directive to your schema in some circumstances but not in others; this depended on how precisely you set up your schema, but not on whether you had disabled the cache control feature. In Apollo Server 3, Apollo Server is now consistent: it never automatically adds the directive definition to your schema. If you use `@cacheControl`, just [define it yourself as shown in the docs](https://www.apollographql.com/docs/apollo-server/performance/caching/#in-your-schema-static). - Top-level exports have changed. E.g., - We no longer re-export the entirety of `graphql-tools` (including `makeExecutableSchema`) from all Apollo Server packages. diff --git a/docs/source/performance/apq.md b/docs/source/performance/apq.md index 5d10c6f67b1..bef78d6e187 100644 --- a/docs/source/performance/apq.md +++ b/docs/source/performance/apq.md @@ -120,7 +120,7 @@ type Author @cacheControl(maxAge: 60) { } ``` -See [the cache control documentation](https://github.com/apollographql/apollo-server/tree/main/packages/apollo-cache-control#add-cache-hints-to-your-schema) for more details, including how to specify hints dynamically inside resolvers, how to set a default `maxAge` for all fields, and how to specify that a field should be cached for specific users only (in which case CDNs should ignore it). For example, to set a default max age other than `0` modify the Apollo Server constructor to include `cacheControl`: +See [the cache control documentation](./caching) for more details, including how to define the `@cacheControl` directive, how to specify hints dynamically inside resolvers, how to set a default `maxAge` for all fields, and how to specify that a field should be cached for specific users only (in which case CDNs should ignore it). For example, to set a default max age other than `0` modify the Apollo Server constructor to include `cacheControl`: ```js const server = new ApolloServer({ diff --git a/docs/source/performance/caching.md b/docs/source/performance/caching.md index e8a22836607..af0d8cceef9 100644 --- a/docs/source/performance/caching.md +++ b/docs/source/performance/caching.md @@ -33,7 +33,21 @@ These details can vary significantly, even among the fields of a single object t ### In your schema (static) -Apollo Server defines the `@cacheControl` directive, which you can use in your schema to define caching behavior either for a [single field](#field-level-definitions), or for _all_ fields that return a particular [type](#type-level-definitions). +Apollo Server recognizes the `@cacheControl` directive, which you can use in your schema to define caching behavior either for a [single field](#field-level-definitions), or for _all_ fields that return a particular [type](#type-level-definitions). + +In order to use the directive in your schema, you need to define it, as well as the enum that is used for one of its arguments; otherwise you will get an error like `Unknown directive "@cacheControl"`. (Older versions of Apollo Server used to automatically insert the definitions in some inconsistent situations; Apollo Server 3 consistently expects you to define them yourself.) Just include the following in your schema file: + +```graphql +enum CacheControlScope { + PUBLIC + PRIVATE +} + +directive @cacheControl( + maxAge: Int + scope: CacheControlScope +) on FIELD_DEFINITION | OBJECT | INTERFACE +``` The `@cacheControl` directive accepts the following arguments: diff --git a/packages/apollo-cache-control/README.md b/packages/apollo-cache-control/README.md index 60ce933a02a..a42e33175e0 100644 --- a/packages/apollo-cache-control/README.md +++ b/packages/apollo-cache-control/README.md @@ -31,7 +31,7 @@ app.use('/graphql', bodyParser.json(), graphqlExpress({ ### Add cache hints to your schema -Cache hints can be added to your schema using directives on your types and fields. When executing your query, these hints will be used to compute an overall cache policy for the response. Hints on fields override hints specified on the target type. +Cache hints can be added to your schema using directives on your types and fields. When executing your query, these hints will be used to compute an overall cache policy for the response. Hints on fields override hints specified on the target type. With Apollo Server 3, you need to include the definition for the `@cacheControl` directive in your schema. ```graphql type Post @cacheControl(maxAge: 240) { @@ -41,6 +41,16 @@ type Post @cacheControl(maxAge: 240) { votes: Int @cacheControl(maxAge: 30) readByCurrentUser: Boolean! @cacheControl(scope: PRIVATE) } + +enum CacheControlScope { + PUBLIC + PRIVATE +} + +directive @cacheControl( + maxAge: Int + scope: CacheControlScope +) on FIELD_DEFINITION | OBJECT | INTERFACE ``` If you need to add cache hints dynamically, you can use a programmatic API from within your resolvers. diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index 83262de0a84..27dc6af5408 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -29,15 +29,12 @@ import { GraphQLService, } from './types'; -import { gql } from './index'; - import { createPlaygroundOptions, PlaygroundRenderPageOptions, } from './playground'; import { generateSchemaHash } from './utils/schemaHash'; -import { isDirectiveDefined } from './utils/isDirectiveDefined'; import { processGraphQLRequest, GraphQLRequestContext, @@ -629,25 +626,6 @@ export class ApolloServerBase { const augmentedTypeDefs = Array.isArray(typeDefs) ? typeDefs : [typeDefs]; - // We augment the typeDefs with the @cacheControl directive and associated - // scope enum, so makeExecutableSchema won't fail SDL validation - - if (!isDirectiveDefined(augmentedTypeDefs, 'cacheControl')) { - augmentedTypeDefs.push( - gql` - enum CacheControlScope { - PUBLIC - PRIVATE - } - - directive @cacheControl( - maxAge: Int - scope: CacheControlScope - ) on FIELD_DEFINITION | OBJECT | INTERFACE - `, - ); - } - return makeExecutableSchema({ typeDefs: augmentedTypeDefs, schemaDirectives, diff --git a/packages/apollo-server-core/src/__tests__/isDirectiveDefined.test.ts b/packages/apollo-server-core/src/__tests__/isDirectiveDefined.test.ts deleted file mode 100644 index a3dbf851ad5..00000000000 --- a/packages/apollo-server-core/src/__tests__/isDirectiveDefined.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { gql } from '../'; -import { isDirectiveDefined } from '../utils/isDirectiveDefined'; - -describe('isDirectiveDefined', () => { - const noCacheControl = ` - type Query { - hello: String - } - `; - const hasCacheControl = ` - type Query { - hello: String - } - - enum CacheControlScope { - PUBLIC - PRIVATE - } - - directive @cacheControl( - maxAge: Int - scope: CacheControlScope - ) on FIELD_DEFINITION | OBJECT | INTERFACE - `; - - describe('When passed an array of DocumentNode', () => { - it('returns false when a directive is not defined', () => { - expect(isDirectiveDefined([gql(noCacheControl)], 'cacheControl')).toBe( - false, - ); - }); - it('returns true when a directive is defined', () => { - expect(isDirectiveDefined([gql(hasCacheControl)], 'cacheControl')).toBe( - true, - ); - }); - }); - - describe('When passed an array of strings', () => { - it('returns false when a directive is not defined', () => { - expect(isDirectiveDefined([noCacheControl], 'cacheControl')).toBe(false); - }); - it('returns true when a directive is defined', () => { - expect(isDirectiveDefined([hasCacheControl], 'cacheControl')).toBe(true); - }); - }); -}); diff --git a/packages/apollo-server-core/src/utils/isDirectiveDefined.ts b/packages/apollo-server-core/src/utils/isDirectiveDefined.ts deleted file mode 100644 index c7682cedd99..00000000000 --- a/packages/apollo-server-core/src/utils/isDirectiveDefined.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DocumentNode, Kind } from 'graphql/language'; -import { gql } from '../'; - -export const isDirectiveDefined = ( - typeDefs: (DocumentNode | string)[], - directiveName: string, -): boolean => { - return typeDefs.some(typeDef => { - if (typeof typeDef === 'string') { - typeDef = gql(typeDef); - } - - return typeDef.definitions.some( - definition => - definition.kind === Kind.DIRECTIVE_DEFINITION && - definition.name.value === directiveName, - ); - }); -}; diff --git a/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts index 143257bd118..9c007724754 100644 --- a/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts @@ -648,6 +648,16 @@ describe('apollo-server-express', () => { cooks: [Cook] pooks: [Pook] } + + enum CacheControlScope { + PUBLIC + PRIVATE + } + + directive @cacheControl( + maxAge: Int + scope: CacheControlScope + ) on FIELD_DEFINITION | OBJECT | INTERFACE `; const resolvers = { diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts index 2d2b2312f95..edfae7a1a66 100644 --- a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -611,6 +611,16 @@ describe('apollo-server-fastify', () => { cooks: [Cook] pooks: [Pook] } + + enum CacheControlScope { + PUBLIC + PRIVATE + } + + directive @cacheControl( + maxAge: Int + scope: CacheControlScope + ) on FIELD_DEFINITION | OBJECT | INTERFACE `; const resolvers = { diff --git a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts index 84e523e7cf7..30105247586 100644 --- a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts +++ b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts @@ -2231,6 +2231,16 @@ export function testApolloServer( uncached: String private: String @cacheControl(maxAge: 9, scope: PRIVATE) } + + enum CacheControlScope { + PUBLIC + PRIVATE + } + + directive @cacheControl( + maxAge: Int + scope: CacheControlScope + ) on FIELD_DEFINITION | OBJECT | INTERFACE `; type FieldName = diff --git a/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts index 170b56051f0..794f909a031 100644 --- a/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts @@ -493,6 +493,16 @@ describe('apollo-server-koa', () => { cooks: [Cook] pooks: [Pook] } + + enum CacheControlScope { + PUBLIC + PRIVATE + } + + directive @cacheControl( + maxAge: Int + scope: CacheControlScope + ) on FIELD_DEFINITION | OBJECT | INTERFACE `; const resolvers = {