Initial Save

This commit is contained in:
jackbeeby
2025-03-28 12:30:19 +11:00
parent e381994f19
commit d8773925e8
9910 changed files with 982718 additions and 0 deletions

39
node_modules/apollo-cache-control/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,39 @@
# Changelog
> **A note on ommitted versions**: Due to the way that the Apollo Server
> monorepo releases occur (via Lerna with _exact_ version pinning), the
> version of the `apollo-cache-control` package is sometimes bumped and
> published despite having no functional changes in its behavior. We will
> always attempt to specifically mention functional changes to the
> `apollo-cache-control` package within this particular `CHANGELOG.md`.
### v0.4.1
* Fix cache hints of `maxAge: 0` to mean "uncachable". (#2197)
* Apply `defaultMaxAge` to scalar fields on the root object. (#2210)
### v0.3.0
* Support calculating Cache-Control HTTP headers when used by `apollo-server@2.0.0`.
### v0.2.0
Moved to the `apollo-server` git repository. No code changes. (There are a
number of other 0.2.x releases with no code changes due to how the
`apollo-server` release process works.)
### v0.1.1
* Fix `defaultMaxAge` feature (introduced in 0.1.0) so that `maxAge: 0` overrides the default, as previously documented.
### v0.1.0
* **New feature**: New `defaultMaxAge` constructor option. (`apollo-server-*` will be updated to allow you to pass constructor options to the extension.)
### v0.0.10
* Update peer dependencies to support `graphql@0.13`.
* Expose `context.cacheControl.cacheHint` to resolvers.
(Older versions exist but have no CHANGELOG entries.)

21
node_modules/apollo-cache-control/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2020 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

119
node_modules/apollo-cache-control/README.md generated vendored Normal file
View File

@@ -0,0 +1,119 @@
# Apollo Cache Control (for Node.js)
This package is used to collect and expose cache control data in the [Apollo Cache Control](https://github.com/apollographql/apollo-cache-control) format.
It relies on instrumenting a GraphQL schema to collect cache control hints, and exposes cache control data for an individual request under `extensions` as part of the GraphQL response.
This data can be consumed by any tool to inform caching and visualize the cache policies that are in effect for a particular request.
Uses for this data include apollo-server-plugin-response-cache (which implements a full response cache) and setting cache-control HTTP headers.
See https://www.apollographql.com/docs/apollo-server/performance/caching/ for more details.
## Usage
### Apollo Server
Apollo Server includes built-in support for Apollo Cache Control from version 1.2.0 onwards.
The only code change required is to add `tracing: true` and `cacheControl: true` to the options passed to the Apollo Server middleware function for your framework of choice. For example, for Express:
```javascript
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema,
context: {},
tracing: true,
cacheControl: true
}));
```
> If you are using `express-graphql`, we recommend you switch to Apollo Server. Both `express-graphql` and Apollo Server are based on the [`graphql-js`](https://github.com/graphql/graphql-js) reference implementation, and switching should only require changing a few lines of code.
### 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.
```graphql
type Post @cacheControl(maxAge: 240) {
id: Int!
title: String
author: Author
votes: Int @cacheControl(maxAge: 30)
readByCurrentUser: Boolean! @cacheControl(scope: PRIVATE)
}
```
If you need to add cache hints dynamically, you can use a programmatic API from within your resolvers.
```javascript
const resolvers = {
Query: {
post: (_, { id }, _, { cacheControl }) => {
cacheControl.setCacheHint({ maxAge: 60 });
return find(posts, { id });
}
}
}
```
If you're using TypeScript, you need the following:
```javascript
import 'apollo-cache-control';
```
If set up correctly, for this query:
```graphql
query {
post(id: 1) {
title
votes
readByCurrentUser
}
}
```
You should receive cache control data in the `extensions` field of your response:
```json
"cacheControl": {
"version": 1,
"hints": [
{
"path": [
"post"
],
"maxAge": 240
},
{
"path": [
"post",
"votes"
],
"maxAge": 30
},
{
"path": [
"post",
"readByCurrentUser"
],
"scope": "PRIVATE"
}
]
}
```
### Setting a default maxAge
The power of cache hints comes from being able to set them precisely to different values on different types and fields based on your understanding of your implementation's semantics. But when getting started with Apollo Cache Control, you might just want to apply the same `maxAge` to most of your resolvers. You can specify a default max age when you set up `cacheControl` in your server. This max age will be applied to all resolvers which don't explicitly set `maxAge` via schema hints (including schema hints on the type that they return) or the programmatic API. You can override this for a particular resolver or type by setting `@cacheControl(maxAge: 0)`. For example, for Express:
```javascript
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema,
context: {},
tracing: true,
cacheControl: {
defaultMaxAge: 5,
},
}));
```

44
node_modules/apollo-cache-control/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,44 @@
import { ResponsePath } from 'graphql';
import { ApolloServerPlugin } from "apollo-server-plugin-base";
export interface CacheControlFormat {
version: 1;
hints: ({
path: (string | number)[];
} & CacheHint)[];
}
export interface CacheHint {
maxAge?: number;
scope?: CacheScope;
}
export declare enum CacheScope {
Public = "PUBLIC",
Private = "PRIVATE"
}
export interface CacheControlExtensionOptions {
defaultMaxAge?: number;
calculateHttpHeaders?: boolean;
stripFormattedExtensions?: boolean;
}
declare module 'graphql/type/definition' {
interface GraphQLResolveInfo {
cacheControl: {
setCacheHint: (hint: CacheHint) => void;
cacheHint: CacheHint;
};
}
}
declare module 'apollo-server-types' {
interface GraphQLRequestContext<TContext> {
overallCachePolicy?: Required<CacheHint> | undefined;
}
}
declare type MapResponsePathHints = Map<ResponsePath, CacheHint>;
export declare const plugin: (options?: CacheControlExtensionOptions) => ApolloServerPlugin;
declare function computeOverallCachePolicy(hints: MapResponsePathHints): Required<CacheHint> | undefined;
declare function addHint(hints: MapResponsePathHints, path: ResponsePath, hint: CacheHint): void;
export declare const __testing__: {
addHint: typeof addHint;
computeOverallCachePolicy: typeof computeOverallCachePolicy;
};
export {};
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,YAAY,EAEb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,CAAC;QAAE,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;KAAE,GAAG,SAAS,CAAC,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,OAAO,QAAQ,yBAAyB,CAAC;IACvC,UAAU,kBAAkB;QAC1B,YAAY,EAAE;YACZ,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;YACxC,SAAS,EAAE,SAAS,CAAC;SACtB,CAAC;KACH;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,qBAAqB,CAAC,QAAQ;QAEtC,kBAAkB,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;KACtD;CACF;AAED,aAAK,oBAAoB,GAAG,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAEzD,eAAO,MAAM,MAAM,aACR,4BAA4B,KACpC,kBAyID,CAAC;AAkDH,iBAAS,yBAAyB,CAChC,KAAK,EAAE,oBAAoB,GAC1B,QAAQ,CAAC,SAAS,CAAC,GAAG,SAAS,CAwBjC;AAED,iBAAS,OAAO,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,QAOhF;AAED,eAAO,MAAM,WAAW;;;CAGvB,CAAC"}

148
node_modules/apollo-cache-control/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,148 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__testing__ = exports.plugin = exports.CacheScope = void 0;
const graphql_1 = require("graphql");
var CacheScope;
(function (CacheScope) {
CacheScope["Public"] = "PUBLIC";
CacheScope["Private"] = "PRIVATE";
})(CacheScope = exports.CacheScope || (exports.CacheScope = {}));
exports.plugin = (options = Object.create(null)) => ({
requestDidStart(requestContext) {
const defaultMaxAge = options.defaultMaxAge || 0;
const hints = new Map();
function setOverallCachePolicyWhenUnset() {
if (!requestContext.overallCachePolicy) {
requestContext.overallCachePolicy = computeOverallCachePolicy(hints);
}
}
return {
executionDidStart: () => ({
executionDidEnd: () => setOverallCachePolicyWhenUnset(),
willResolveField({ info }) {
let hint = {};
const targetType = graphql_1.getNamedType(info.returnType);
if (targetType instanceof graphql_1.GraphQLObjectType ||
targetType instanceof graphql_1.GraphQLInterfaceType) {
if (targetType.astNode) {
hint = mergeHints(hint, cacheHintFromDirectives(targetType.astNode.directives));
}
}
const fieldDef = info.parentType.getFields()[info.fieldName];
if (fieldDef.astNode) {
hint = mergeHints(hint, cacheHintFromDirectives(fieldDef.astNode.directives));
}
if ((targetType instanceof graphql_1.GraphQLObjectType ||
targetType instanceof graphql_1.GraphQLInterfaceType ||
!info.path.prev) &&
hint.maxAge === undefined) {
hint.maxAge = defaultMaxAge;
}
if (hint.maxAge !== undefined || hint.scope !== undefined) {
addHint(hints, info.path, hint);
}
info.cacheControl = {
setCacheHint: (hint) => {
addHint(hints, info.path, hint);
},
cacheHint: hint,
};
},
}),
responseForOperation() {
setOverallCachePolicyWhenUnset();
return null;
},
willSendResponse(requestContext) {
const { response, overallCachePolicy: overallCachePolicyOverride, } = requestContext;
if (response.errors) {
return;
}
const overallCachePolicy = overallCachePolicyOverride ||
(requestContext.overallCachePolicy =
computeOverallCachePolicy(hints));
if (overallCachePolicy &&
options.calculateHttpHeaders &&
response.http) {
response.http.headers.set('Cache-Control', `max-age=${overallCachePolicy.maxAge}, ${overallCachePolicy.scope.toLowerCase()}`);
}
if (options.stripFormattedExtensions !== false)
return;
const extensions = response.extensions || (response.extensions = Object.create(null));
if (typeof extensions.cacheControl !== 'undefined') {
throw new Error("The cacheControl information already existed.");
}
extensions.cacheControl = {
version: 1,
hints: Array.from(hints).map(([path, hint]) => (Object.assign({ path: [...graphql_1.responsePathAsArray(path)] }, hint))),
};
}
};
}
});
function cacheHintFromDirectives(directives) {
if (!directives)
return undefined;
const cacheControlDirective = directives.find(directive => directive.name.value === 'cacheControl');
if (!cacheControlDirective)
return undefined;
if (!cacheControlDirective.arguments)
return undefined;
const maxAgeArgument = cacheControlDirective.arguments.find(argument => argument.name.value === 'maxAge');
const scopeArgument = cacheControlDirective.arguments.find(argument => argument.name.value === 'scope');
return {
maxAge: maxAgeArgument &&
maxAgeArgument.value &&
maxAgeArgument.value.kind === 'IntValue'
? parseInt(maxAgeArgument.value.value)
: undefined,
scope: scopeArgument &&
scopeArgument.value &&
scopeArgument.value.kind === 'EnumValue'
? scopeArgument.value.value
: undefined,
};
}
function mergeHints(hint, otherHint) {
if (!otherHint)
return hint;
return {
maxAge: otherHint.maxAge !== undefined ? otherHint.maxAge : hint.maxAge,
scope: otherHint.scope || hint.scope,
};
}
function computeOverallCachePolicy(hints) {
let lowestMaxAge = undefined;
let scope = CacheScope.Public;
for (const hint of hints.values()) {
if (hint.maxAge !== undefined) {
lowestMaxAge =
lowestMaxAge !== undefined
? Math.min(lowestMaxAge, hint.maxAge)
: hint.maxAge;
}
if (hint.scope === CacheScope.Private) {
scope = CacheScope.Private;
}
}
return lowestMaxAge
? {
maxAge: lowestMaxAge,
scope,
}
: undefined;
}
function addHint(hints, path, hint) {
const existingCacheHint = hints.get(path);
if (existingCacheHint) {
hints.set(path, mergeHints(existingCacheHint, hint));
}
else {
hints.set(path, hint);
}
}
exports.__testing__ = {
addHint,
computeOverallCachePolicy,
};
//# sourceMappingURL=index.js.map

1
node_modules/apollo-cache-control/dist/index.js.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

21
node_modules/apollo-cache-control/package.json generated vendored Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "apollo-cache-control",
"version": "0.15.0",
"description": "A GraphQL extension for cache control",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "MIT",
"repository": "apollographql/apollo-cache-control-js",
"author": "Apollo <opensource@apollographql.com>",
"engines": {
"node": ">=6.0"
},
"dependencies": {
"apollo-server-env": "^3.2.0",
"apollo-server-plugin-base": "^0.14.0"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
},
"gitHead": "91de501bb389c07ccfc5e684811153267b91e9ac"
}

View File

@@ -0,0 +1,248 @@
import { buildSchemaWithCacheControlSupport } from './cacheControlSupport';
import { CacheScope } from '../';
import { collectCacheControlHints } from './collectCacheControlHints';
describe('@cacheControl directives', () => {
it('should set maxAge: 0 and no scope for a field without cache hints', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid
}
type Droid {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
);
expect(hints).toContainEqual({ path: ['droid'], maxAge: 0 });
});
it('should set maxAge: 0 and no scope for a top-level scalar field without cache hints', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
name: String
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
name
}
`,
);
expect(hints).toContainEqual({ path: ['name'], maxAge: 0 });
});
it('should set maxAge to the default and no scope for a field without cache hints', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid
}
type Droid {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({ path: ['droid'], maxAge: 10 });
});
it('should set the specified maxAge from a cache hint on the field', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid @cacheControl(maxAge: 60)
}
type Droid {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({ path: ['droid'], maxAge: 60 });
});
it('should set the specified maxAge for a field from a cache hint on the target type', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid
}
type Droid @cacheControl(maxAge: 60) {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({ path: ['droid'], maxAge: 60 });
});
it('should overwrite the default maxAge when maxAge=0 is specified on the type', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid
}
type Droid @cacheControl(maxAge: 0) {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({ path: ['droid'], maxAge: 0 });
});
it('should override the maxAge from the target type with that specified on a field', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid @cacheControl(maxAge: 120)
}
type Droid @cacheControl(maxAge: 60) {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({ path: ['droid'], maxAge: 120 });
});
it('should override the maxAge from the target type with that specified on a field, keeping the scope', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid @cacheControl(maxAge: 120)
}
type Droid @cacheControl(maxAge: 60, scope: PRIVATE) {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({
path: ['droid'],
maxAge: 120,
scope: CacheScope.Private,
});
});
it('should override the scope from the target type with that specified on a field', async () => {
const schema = buildSchemaWithCacheControlSupport(`
type Query {
droid(id: ID!): Droid @cacheControl(scope: PRIVATE)
}
type Droid @cacheControl(maxAge: 60, scope: PUBLIC) {
id: ID!
name: String!
}
`);
const hints = await collectCacheControlHints(
schema,
`
query {
droid(id: 2001) {
name
}
}
`,
{ defaultMaxAge: 10 },
);
expect(hints).toContainEqual({
path: ['droid'],
maxAge: 60,
scope: CacheScope.Private,
});
});
});

View File

@@ -0,0 +1,172 @@
import { ResponsePath, GraphQLError } from 'graphql';
import { Headers } from 'apollo-server-env';
import {
CacheScope,
CacheControlExtensionOptions,
CacheHint,
__testing__,
plugin,
} from '../';
const { addHint, computeOverallCachePolicy } = __testing__;
import {
GraphQLRequestContextWillSendResponse,
GraphQLResponse,
} from 'apollo-server-plugin-base';
import pluginTestHarness from 'apollo-server-core/dist/utils/pluginTestHarness';
describe('plugin', () => {
describe('willSendResponse', () => {
function makePluginWithOptions({
pluginInitializationOptions,
overallCachePolicy,
errors = false,
}: {
pluginInitializationOptions?: CacheControlExtensionOptions;
overallCachePolicy?: Required<CacheHint>;
errors?: boolean;
} = Object.create(null)) {
const pluginInstance = plugin(pluginInitializationOptions);
return pluginTestHarness({
pluginInstance,
overallCachePolicy,
// This query needs to pass graphql validation
graphqlRequest: { query: 'query { hello }' },
executor: () => {
const response: GraphQLResponse = {
http: {
headers: new Headers(),
},
data: { test: 'test' },
};
if (errors) {
response.errors = [new GraphQLError('Test Error')];
}
return response;
},
});
}
describe('HTTP cache-control header', () => {
const overallCachePolicy: Required<CacheHint> = {
maxAge: 300,
scope: CacheScope.Public,
};
it('is set when calculateHttpHeaders is set to true', async () => {
const requestContext = await makePluginWithOptions({
pluginInitializationOptions: {
calculateHttpHeaders: true,
},
overallCachePolicy,
});
expect(requestContext.response.http!.headers.get('Cache-Control')).toBe(
'max-age=300, public',
);
});
const shouldNotSetCacheControlHeader = (
requestContext: GraphQLRequestContextWillSendResponse<any>,
) => {
expect(
requestContext.response.http!.headers.get('Cache-Control'),
).toBeNull();
};
it('is not set when calculateHttpHeaders is set to false', async () => {
const requestContext = await makePluginWithOptions({
pluginInitializationOptions: {
calculateHttpHeaders: false,
},
overallCachePolicy,
});
shouldNotSetCacheControlHeader(requestContext);
});
it('is not set if response has errors', async () => {
const requestContext = await makePluginWithOptions({
pluginInitializationOptions: {
calculateHttpHeaders: false,
},
overallCachePolicy,
errors: true,
});
shouldNotSetCacheControlHeader(requestContext);
});
it('does not set cache-control header if there is no overall cache policy', async () => {
const requestContext = await makePluginWithOptions({
pluginInitializationOptions: {
calculateHttpHeaders: false,
},
overallCachePolicy: undefined,
errors: true,
});
shouldNotSetCacheControlHeader(requestContext);
});
});
});
describe('computeOverallCachePolicy', () => {
const responsePath: ResponsePath = {
key: 'test',
prev: undefined,
};
const responseSubPath: ResponsePath = {
key: 'subTest',
prev: responsePath,
};
const responseSubSubPath: ResponsePath = {
key: 'subSubTest',
prev: responseSubPath,
};
const hints = new Map();
afterEach(() => hints.clear());
it('returns undefined without cache hints', () => {
const cachePolicy = computeOverallCachePolicy(hints);
expect(cachePolicy).toBeUndefined();
});
it('returns lowest max age value', () => {
addHint(hints, responsePath, { maxAge: 10 });
addHint(hints, responseSubPath, { maxAge: 20 });
const cachePolicy = computeOverallCachePolicy(hints);
expect(cachePolicy).toHaveProperty('maxAge', 10);
});
it('returns undefined if any cache hint has a maxAge of 0', () => {
addHint(hints, responsePath, { maxAge: 120 });
addHint(hints, responseSubPath, { maxAge: 0 });
addHint(hints, responseSubSubPath, { maxAge: 20 });
const cachePolicy = computeOverallCachePolicy(hints);
expect(cachePolicy).toBeUndefined();
});
it('returns PUBLIC scope by default', () => {
addHint(hints, responsePath, { maxAge: 10 });
const cachePolicy = computeOverallCachePolicy(hints);
expect(cachePolicy).toHaveProperty('scope', CacheScope.Public);
});
it('returns PRIVATE scope if any cache hint has PRIVATE scope', () => {
addHint(hints, responsePath, {
maxAge: 10,
scope: CacheScope.Public,
});
addHint(hints, responseSubPath, {
maxAge: 10,
scope: CacheScope.Private,
});
const cachePolicy = computeOverallCachePolicy(hints);
expect(cachePolicy).toHaveProperty('scope', CacheScope.Private);
});
});
});

View File

@@ -0,0 +1,33 @@
import { buildSchema } from 'graphql';
import { makeExecutableSchema } from 'graphql-tools';
type FirstArg<F> = F extends (arg: infer A) => any ? A : never;
export function augmentTypeDefsWithCacheControlSupport(typeDefs: string) {
return (
`
enum CacheControlScope {
PUBLIC
PRIVATE
}
directive @cacheControl(
maxAge: Int
scope: CacheControlScope
) on FIELD_DEFINITION | OBJECT | INTERFACE
` + typeDefs
);
}
export function buildSchemaWithCacheControlSupport(source: string) {
return buildSchema(augmentTypeDefsWithCacheControlSupport(source));
}
export function makeExecutableSchemaWithCacheControlSupport(
options: FirstArg<typeof makeExecutableSchema> & { typeDefs: string },
) {
return makeExecutableSchema({
...options,
typeDefs: augmentTypeDefsWithCacheControlSupport(options.typeDefs),
});
}

View File

@@ -0,0 +1,41 @@
import { GraphQLSchema, graphql } from 'graphql';
import {
CacheHint,
CacheControlExtensionOptions,
plugin,
} from '../';
import pluginTestHarness from 'apollo-server-core/dist/utils/pluginTestHarness';
export async function collectCacheControlHints(
schema: GraphQLSchema,
source: string,
options?: CacheControlExtensionOptions,
): Promise<CacheHint[]> {
// Because this test helper looks at the formatted extensions, we always want
// to include them in the response rather than allow them to be stripped
// out.
const pluginInstance = plugin({
...options,
stripFormattedExtensions: false,
});
const requestContext = await pluginTestHarness({
pluginInstance,
schema,
graphqlRequest: {
query: source,
},
executor: async (requestContext) => {
return await graphql({
schema,
source: requestContext.request.query,
contextValue: requestContext.context,
});
}
});
expect(requestContext.response.errors).toBeUndefined();
return requestContext.response.extensions!.cacheControl.hints;
}

View File

@@ -0,0 +1,163 @@
import {
GraphQLScalarType,
GraphQLFieldResolver,
GraphQLTypeResolver,
GraphQLIsTypeOfFn,
} from 'graphql';
import { makeExecutableSchemaWithCacheControlSupport } from './cacheControlSupport';
import { CacheScope } from '../';
import { collectCacheControlHints } from './collectCacheControlHints';
export interface GraphQLResolvers {
[fieldName: string]: (() => any) | GraphQLResolverObject | GraphQLScalarType;
}
export type GraphQLResolverObject = {
[fieldName: string]: GraphQLFieldResolver<any, any> | GraphQLResolverOptions;
};
export interface GraphQLResolverOptions {
resolve?: GraphQLFieldResolver<any, any>;
subscribe?: GraphQLFieldResolver<any, any>;
__resolveType?: GraphQLTypeResolver<any, any>;
__isTypeOf?: GraphQLIsTypeOfFn<any, any>;
}
describe('dynamic cache control', () => {
it('should set the maxAge for a field from a dynamic cache hint', 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.setCacheHint({ 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).toContainEqual({ path: ['droid'], maxAge: 60 });
});
it('should set the scope for a field from a dynamic cache hint', async () => {
const typeDefs = `
type Query {
droid(id: ID!): Droid @cacheControl(maxAge: 60)
}
type Droid {
id: ID!
name: String!
}
`;
const resolvers: GraphQLResolvers = {
Query: {
droid: (_source, _args, _context, { cacheControl }) => {
cacheControl.setCacheHint({ scope: CacheScope.Private });
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).toContainEqual({
path: ['droid'],
maxAge: 60,
scope: CacheScope.Private,
});
});
it('should override the maxAge set for a field from a dynamic cache hint', async () => {
const typeDefs = `
type Query {
droid(id: ID!): Droid @cacheControl(maxAge: 60)
}
type Droid {
id: ID!
name: String!
}
`;
const resolvers: GraphQLResolvers = {
Query: {
droid: (_source, _args, _context, { cacheControl }) => {
cacheControl.setCacheHint({ maxAge: 120 });
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).toContainEqual({ path: ['droid'], maxAge: 120 });
});
});

View File

@@ -0,0 +1,7 @@
{
"extends": "../../../../tsconfig.test.base",
"include": ["**/*"],
"references": [
{ "path": "../../" },
]
}

281
node_modules/apollo-cache-control/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,281 @@
import {
DirectiveNode,
getNamedType,
GraphQLInterfaceType,
GraphQLObjectType,
ResponsePath,
responsePathAsArray,
} from 'graphql';
import { ApolloServerPlugin } from "apollo-server-plugin-base";
export interface CacheControlFormat {
version: 1;
hints: ({ path: (string | number)[] } & CacheHint)[];
}
export interface CacheHint {
maxAge?: number;
scope?: CacheScope;
}
export enum CacheScope {
Public = 'PUBLIC',
Private = 'PRIVATE',
}
export interface CacheControlExtensionOptions {
defaultMaxAge?: number;
// TODO: We should replace these with
// more appropriately named options.
calculateHttpHeaders?: boolean;
stripFormattedExtensions?: boolean;
}
declare module 'graphql/type/definition' {
interface GraphQLResolveInfo {
cacheControl: {
setCacheHint: (hint: CacheHint) => void;
cacheHint: CacheHint;
};
}
}
declare module 'apollo-server-types' {
interface GraphQLRequestContext<TContext> {
// Not readonly: plugins can set it.
overallCachePolicy?: Required<CacheHint> | undefined;
}
}
type MapResponsePathHints = Map<ResponsePath, CacheHint>;
export const plugin = (
options: CacheControlExtensionOptions = Object.create(null),
): ApolloServerPlugin => ({
requestDidStart(requestContext) {
const defaultMaxAge: number = options.defaultMaxAge || 0;
const hints: MapResponsePathHints = new Map();
function setOverallCachePolicyWhenUnset() {
if (!requestContext.overallCachePolicy) {
requestContext.overallCachePolicy = computeOverallCachePolicy(hints);
}
}
return {
executionDidStart: () => ({
executionDidEnd: () => setOverallCachePolicyWhenUnset(),
willResolveField({ info }) {
let hint: CacheHint = {};
// If this field's resolver returns an object or interface, look for
// hints on that return type.
const targetType = getNamedType(info.returnType);
if (
targetType instanceof GraphQLObjectType ||
targetType instanceof GraphQLInterfaceType
) {
if (targetType.astNode) {
hint = mergeHints(
hint,
cacheHintFromDirectives(targetType.astNode.directives),
);
}
}
// Look for hints on the field itself (on its parent type), taking
// precedence over previously calculated hints.
const fieldDef = info.parentType.getFields()[info.fieldName];
if (fieldDef.astNode) {
hint = mergeHints(
hint,
cacheHintFromDirectives(fieldDef.astNode.directives),
);
}
// If this resolver returns an object 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.)
if (
(targetType instanceof GraphQLObjectType ||
targetType instanceof GraphQLInterfaceType ||
!info.path.prev) &&
hint.maxAge === undefined
) {
hint.maxAge = defaultMaxAge;
}
if (hint.maxAge !== undefined || hint.scope !== undefined) {
addHint(hints, info.path, hint);
}
info.cacheControl = {
setCacheHint: (hint: CacheHint) => {
addHint(hints, info.path, hint);
},
cacheHint: hint,
};
},
}),
responseForOperation() {
// We are not supplying an answer, we are only setting the cache
// policy if it's not set! Therefore, we return null.
setOverallCachePolicyWhenUnset();
return null;
},
willSendResponse(requestContext) {
const {
response,
overallCachePolicy: overallCachePolicyOverride,
} = requestContext;
// If there are any errors, we don't consider this cacheable.
if (response.errors) {
return;
}
// Use the override by default, but if it's not overridden, set our
// own computation onto the `requestContext` for other plugins to read.
const overallCachePolicy =
overallCachePolicyOverride ||
(requestContext.overallCachePolicy =
computeOverallCachePolicy(hints));
if (
overallCachePolicy &&
options.calculateHttpHeaders &&
response.http
) {
response.http.headers.set(
'Cache-Control',
`max-age=${
overallCachePolicy.maxAge
}, ${overallCachePolicy.scope.toLowerCase()}`,
);
}
// We should have to explicitly ask to leave the formatted extension in,
// or pass the old-school `cacheControl: true` (as interpreted by
// apollo-server-core/ApolloServer), in order to include the
// old engineproxy-aimed extensions. Specifically, we want users of
// apollo-server-plugin-response-cache to be able to specify
// `cacheControl: {defaultMaxAge: 600}` without accidentally turning on
// the extension formatting.
if (options.stripFormattedExtensions !== false) return;
const extensions =
response.extensions || (response.extensions = Object.create(null));
if (typeof extensions.cacheControl !== 'undefined') {
throw new Error("The cacheControl information already existed.");
}
extensions.cacheControl = {
version: 1,
hints: Array.from(hints).map(([path, hint]) => ({
path: [...responsePathAsArray(path)],
...hint,
})),
};
}
}
}
});
function cacheHintFromDirectives(
directives: ReadonlyArray<DirectiveNode> | undefined,
): CacheHint | undefined {
if (!directives) return undefined;
const cacheControlDirective = directives.find(
directive => directive.name.value === 'cacheControl',
);
if (!cacheControlDirective) return undefined;
if (!cacheControlDirective.arguments) return undefined;
const maxAgeArgument = cacheControlDirective.arguments.find(
argument => argument.name.value === 'maxAge',
);
const scopeArgument = cacheControlDirective.arguments.find(
argument => argument.name.value === 'scope',
);
// TODO: Add proper typechecking of arguments
return {
maxAge:
maxAgeArgument &&
maxAgeArgument.value &&
maxAgeArgument.value.kind === 'IntValue'
? parseInt(maxAgeArgument.value.value)
: undefined,
scope:
scopeArgument &&
scopeArgument.value &&
scopeArgument.value.kind === 'EnumValue'
? (scopeArgument.value.value as CacheScope)
: undefined,
};
}
function mergeHints(
hint: CacheHint,
otherHint: CacheHint | undefined,
): CacheHint {
if (!otherHint) return hint;
return {
maxAge: otherHint.maxAge !== undefined ? otherHint.maxAge : hint.maxAge,
scope: otherHint.scope || hint.scope,
};
}
function computeOverallCachePolicy(
hints: MapResponsePathHints,
): Required<CacheHint> | undefined {
let lowestMaxAge: number | undefined = undefined;
let scope: CacheScope = CacheScope.Public;
for (const hint of hints.values()) {
if (hint.maxAge !== undefined) {
lowestMaxAge =
lowestMaxAge !== undefined
? Math.min(lowestMaxAge, hint.maxAge)
: hint.maxAge;
}
if (hint.scope === CacheScope.Private) {
scope = CacheScope.Private;
}
}
// If maxAge is 0, then we consider it uncacheable so it doesn't matter what
// the scope was.
return lowestMaxAge
? {
maxAge: lowestMaxAge,
scope,
}
: undefined;
}
function addHint(hints: MapResponsePathHints, path: ResponsePath, hint: CacheHint) {
const existingCacheHint = hints.get(path);
if (existingCacheHint) {
hints.set(path, mergeHints(existingCacheHint, hint));
} else {
hints.set(path, hint);
}
}
export const __testing__ = {
addHint,
computeOverallCachePolicy,
};