-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Previously, all the logic around updating the cache policy with new hints was hardcoded inside the cache control plugin. Also, `overallCachePolicy` undefined was used to represent two distinct states: "we don't know anything about cache policy yet" and "definitely uncacheable". There was a `PolicyUpdater` class that was part of the CC plugin that handled the logic of updating a policy. After this PR, `overallCachePolicy` is always defined. The former case is `maxAge === undefined` and the latter case is `maxAge === 0`. This object also has some helpful methods that don't exist on `CacheHint`: - `restrict` and `replace` applies a `CacheHint` to it; the former can only make the policy more restrictive, whereas the latter overrides the current values of whatever fields are defined in its argument - `cacheablePolicy` either returns null (if we have no information about cacheability or if we know it's not cacheable) or returns a `CacheHint` whose maxAge is positive and whose scope is defined. These make `PolicyUpdater` unnecessary. Inside a resolver, `info.cacheControl.cacheHint` also has these methods! So while you can still do `info.cacheControl.setCacheHint(hint)` (which is identical to `info.cacheControl.cacheHint.replace(hint)`), you also have the ability to do `info.cacheControl.cacheHint.restrict(hint)` if that's what you'd like to do. Additionally, the `maxAge` and `scope` fields on `info.cacheControl.cacheHint` now update if you call `restrict`, `replace`, or `setCacheHint`. Also, resolvers now have access to `info.cacheControl.cacheHintFromType` which it can use to extract cache hints from the schema. (This may be helpful for a resolver that returns an abstract type, such as Federation's `Query._entities`). Also, `extend type @CacheControl(...)` directives are now honored instead of silently ignored. There's also a cache for directive parsing instead of examining the AST in detail every single time a field is resolved. This change is mostly backwards-compatible, unless you accessed `requestContext.overallCachePolicy` directly (eg from a plugin). In that case: - If you relied on `requestContext.overallCachePolicy` being undefined to mean "not cacheable", you should instead see if `requestContext.overallCachePolicy.policyIfCacheable()` is null. - If you assigned a `CacheHint` to `requestContext.overallCachePolicy`, you should pass it to `requestContext.overallCachePolicy.replace()` instead.
- Loading branch information
Showing
14 changed files
with
349 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
packages/apollo-server-core/src/__tests__/cachePolicy.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { CachePolicy, CacheScope } from 'apollo-server-types'; | ||
import { newCachePolicy } from '../cachePolicy'; | ||
|
||
describe('newCachePolicy', () => { | ||
let cachePolicy: CachePolicy; | ||
beforeEach(() => { | ||
cachePolicy = newCachePolicy(); | ||
}); | ||
|
||
it('starts uncacheable', () => { | ||
expect(cachePolicy.maxAge).toBeUndefined(); | ||
expect(cachePolicy.scope).toBeUndefined(); | ||
}); | ||
|
||
it('restricting maxAge positive makes restricted', () => { | ||
cachePolicy.restrict({ maxAge: 10 }); | ||
}); | ||
|
||
it('restricting maxAge 0 makes restricted', () => { | ||
cachePolicy.restrict({ maxAge: 0 }); | ||
}); | ||
|
||
it('restricting scope to private makes restricted', () => { | ||
cachePolicy.restrict({ scope: CacheScope.Private }); | ||
}); | ||
|
||
it('returns lowest max age value', () => { | ||
cachePolicy.restrict({ maxAge: 10 }); | ||
cachePolicy.restrict({ maxAge: 20 }); | ||
|
||
expect(cachePolicy.maxAge).toBe(10); | ||
}); | ||
|
||
it('returns lowest max age value in other order', () => { | ||
cachePolicy.restrict({ maxAge: 20 }); | ||
cachePolicy.restrict({ maxAge: 10 }); | ||
|
||
expect(cachePolicy.maxAge).toBe(10); | ||
}); | ||
|
||
it('maxAge 0 if any cache hint has a maxAge of 0', () => { | ||
cachePolicy.restrict({ maxAge: 120 }); | ||
cachePolicy.restrict({ maxAge: 0 }); | ||
cachePolicy.restrict({ maxAge: 20 }); | ||
|
||
expect(cachePolicy.maxAge).toBe(0); | ||
}); | ||
|
||
it('returns undefined if first cache hint has a maxAge of 0', () => { | ||
cachePolicy.restrict({ maxAge: 0 }); | ||
cachePolicy.restrict({ maxAge: 20 }); | ||
|
||
expect(cachePolicy.maxAge).toBe(0); | ||
}); | ||
|
||
it('only restricting maxAge keeps scope undefined', () => { | ||
cachePolicy.restrict({ maxAge: 10 }); | ||
|
||
expect(cachePolicy.scope).toBeUndefined(); | ||
}); | ||
|
||
it('returns PRIVATE scope if any cache hint has PRIVATE scope', () => { | ||
cachePolicy.restrict({ | ||
maxAge: 10, | ||
scope: CacheScope.Public, | ||
}); | ||
cachePolicy.restrict({ | ||
maxAge: 10, | ||
scope: CacheScope.Private, | ||
}); | ||
|
||
expect(cachePolicy).toHaveProperty('scope', CacheScope.Private); | ||
}); | ||
|
||
it('policyIfCacheable', () => { | ||
expect(cachePolicy.policyIfCacheable()).toBeNull(); | ||
|
||
cachePolicy.restrict({ scope: CacheScope.Private }); | ||
expect(cachePolicy.scope).toBe(CacheScope.Private); | ||
expect(cachePolicy.policyIfCacheable()).toBeNull(); | ||
|
||
cachePolicy.restrict({ maxAge: 10 }); | ||
expect(cachePolicy).toMatchObject({ | ||
maxAge: 10, | ||
scope: CacheScope.Private, | ||
}); | ||
expect(cachePolicy.policyIfCacheable()).toStrictEqual({ | ||
maxAge: 10, | ||
scope: CacheScope.Private, | ||
}); | ||
|
||
cachePolicy.restrict({ maxAge: 0 }); | ||
expect(cachePolicy).toMatchObject({ | ||
maxAge: 0, | ||
scope: CacheScope.Private, | ||
}); | ||
expect(cachePolicy.policyIfCacheable()).toBeNull(); | ||
}); | ||
|
||
it('replace', () => { | ||
cachePolicy.restrict({ maxAge: 10, scope: CacheScope.Private }); | ||
cachePolicy.replace({ maxAge: 20, scope: CacheScope.Public }); | ||
|
||
expect(cachePolicy).toMatchObject({ | ||
maxAge: 20, | ||
scope: CacheScope.Public, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { CacheHint, CachePolicy, CacheScope } from 'apollo-server-types'; | ||
|
||
export function newCachePolicy(): CachePolicy { | ||
return { | ||
maxAge: undefined, | ||
scope: undefined, | ||
restrict(hint: CacheHint) { | ||
if ( | ||
hint.maxAge !== undefined && | ||
(this.maxAge === undefined || hint.maxAge < this.maxAge) | ||
) { | ||
this.maxAge = hint.maxAge; | ||
} | ||
if (hint.scope !== undefined && this.scope !== CacheScope.Private) { | ||
this.scope = hint.scope; | ||
} | ||
}, | ||
replace(hint: CacheHint) { | ||
if (hint.maxAge !== undefined) { | ||
this.maxAge = hint.maxAge; | ||
} | ||
if (hint.scope !== undefined) { | ||
this.scope = hint.scope; | ||
} | ||
}, | ||
policyIfCacheable() { | ||
if (this.maxAge === undefined || this.maxAge === 0) { | ||
return null; | ||
} | ||
return { maxAge: this.maxAge, scope: this.scope ?? CacheScope.Public }; | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.