Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cache control: don't apply default max age until after resolver runs #5492

Merged
merged 1 commit into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The version headers in this history reflect the versions of Apollo Server itself

## vNEXT

- `apollo-server-core`: The default `maxAge` (which defaults to 0) for a field should only be applied if no dynamic cache control hint is set. Specifically, if you call the (new in 3.0.0) function `info.cacheControl.cacheHint.restrict({ maxAge: 60 })`, it should set `maxAge` to 60 even if the default max age is lower. (This bug fix is the behavior that was intended for 3.0.0, and primarily affects the behavior of functions added in Apollo Server 3. This does mean that checking `info.cacheControl.cacheHint` now only shows explicitly-set `maxAge` and not the default, but this seems like it will be helpful since it lets you differentiate between the two similar circumstances.) [PR #5492](https://github.com/apollographql/apollo-server/pull/5492)
- `apollo-server-lambda`: Fix TypeScript types for `context` function. (In 3.0.0, the TS types for the `context` function were accidentally inherited from `apollo-server-express` instead of using the correct Lambda-specific types). [PR #5481](https://github.com/apollographql/apollo-server/pull/5481)

## v3.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,50 @@ describe('dynamic cache control', () => {
expect(hints).toStrictEqual(new Map([['droid', { maxAge: 60 }]]));
});

it('can use restrict to set the maxAge for a field', async () => {
const typeDefs = `
type Query {
droid(id: ID!): Droid
}

type Droid {
id: ID!
name: String!
}
`;

const resolvers: GraphQLResolvers = {
Query: {
droid: (_source, _args, _context, { cacheControl }) => {
cacheControl.cacheHint.restrict({ maxAge: 60 });
return {
id: 2001,
name: 'R2-D2',
};
},
},
};

const schema = makeExecutableSchemaWithCacheControlSupport({
typeDefs,
resolvers,
});

const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);

expect(hints).toStrictEqual(new Map([['droid', { maxAge: 60 }]]));
});

it('should set the scope for a field from a dynamic cache hint', async () => {
const typeDefs = `
type Query {
Expand Down
51 changes: 29 additions & 22 deletions packages/apollo-server-core/src/plugin/cacheControl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,28 +186,6 @@ export function ApolloServerPluginCacheControl(
fieldPolicy.replace(fieldAnnotation);
}

// If this field returns a composite type or is a root field and
// we haven't seen an explicit maxAge hint, set the maxAge to 0
// (uncached) or the default if specified in the constructor.
// (Non-object fields by default are assumed to inherit their
// cacheability from their parents. But on the other hand, while
// root non-object fields can get explicit hints from their
// definition on the Query/Mutation object, if that doesn't exist
// then there's no parent field that would assign the default
// maxAge, so we do it here.)
//
// You can disable this on a non-root field by writing
// `@cacheControl(inheritMaxAge: true)` on it. If you do this,
// then its children will be treated like root paths, since there
// is no parent maxAge to inherit.
if (
fieldPolicy.maxAge === undefined &&
((isCompositeType(targetType) && !inheritMaxAge) ||
!info.path.prev)
) {
fieldPolicy.restrict({ maxAge: defaultMaxAge });
}

info.cacheControl = {
setCacheHint: (dynamicHint: CacheHint) => {
fieldPolicy.replace(dynamicHint);
Expand All @@ -221,6 +199,35 @@ export function ApolloServerPluginCacheControl(
// "undo" the effect on overallCachePolicy of a static hint that
// gets refined by a dynamic hint.
return () => {
// If this field returns a composite type or is a root field and
// we haven't seen an explicit maxAge hint, set the maxAge to 0
// (uncached) or the default if specified in the constructor.
// (Non-object fields by default are assumed to inherit their
// cacheability from their parents. But on the other hand, while
// root non-object fields can get explicit hints from their
// definition on the Query/Mutation object, if that doesn't
// exist then there's no parent field that would assign the
// default maxAge, so we do it here.)
//
// You can disable this on a non-root field by writing
// `@cacheControl(inheritMaxAge: true)` on it. If you do this,
// then its children will be treated like root paths, since
// there is no parent maxAge to inherit.
//
// We do this in the end hook so that dynamic cache control
// prevents it from happening (eg,
// `info.cacheControl.cacheHint.restrict({maxAge: 60})` should
// work rather than doing nothing because we've already set the
// max age to the default of 0). This also lets resolvers assume
// any hint in `info.cacheControl.cacheHint` was explicitly set.
if (
fieldPolicy.maxAge === undefined &&
((isCompositeType(targetType) && !inheritMaxAge) ||
!info.path.prev)
) {
fieldPolicy.restrict({ maxAge: defaultMaxAge });
}

if (__testing__cacheHints && isRestricted(fieldPolicy)) {
const path = responsePathAsArray(info.path).join('.');
if (__testing__cacheHints.has(path)) {
Expand Down