Add stale-while-revalidate cache headers

Oh yay, I'm pleased with this! I hope it works out well!

stale-while-revalidate is an HTTP caching feature that gives us the ability to still serve relatively static content like item pages ASAP, while also making sure users generally see updates quickly.

The trick is that we declare a period of time where, you can still serve the data from the cache, but you should _then_ go re-fetch the latest data in the background for next time. This works on end users and on the CDN!

I've scanned the basic wardrobe and homepage stuff and brought them up-to-date, and gave particular attention to the item page, which I hope can be very very snappy now! :3

Note to self: Vercel says we can manually clear out a stale-while-revalidate resource by requesting it with `Pragma: no-cache`. I'm not sure it will listen to us for _fresh_ resources, though, so I'm not sure we can actually use that to flush things out in the way I had been hoping until writing this sentence lol :p
This commit is contained in:
Emi Matchu 2021-02-02 19:07:38 -08:00
parent c00df62bdc
commit d3b9f72e67
7 changed files with 561 additions and 48 deletions

View file

@ -19,15 +19,15 @@
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"apollo-link-persisted-queries": "^0.2.2",
"apollo-server": "^2.12.0",
"apollo-server-core": "^2.12.0",
"apollo-server-env": "^2.4.3",
"apollo-server": "^2.19.2",
"apollo-server-core": "^2.19.2",
"apollo-server-env": "^3.0.0",
"aws-sdk": "^2.726.0",
"canvas": "^2.6.1",
"dataloader": "^2.0.0",
"dompurify": "^2.2.0",
"framer-motion": "^3.1.1",
"graphql": "^15.0.0",
"graphql": "^15.5.0",
"honeycomb-beeline": "^2.2.0",
"immer": "^8.0.1",
"jimp": "^0.14.0",

View file

@ -4,6 +4,9 @@ const { gql, makeExecutableSchema } = require("apollo-server");
const { getUserIdFromToken } = require("./auth");
const connectToDb = require("./db");
const buildLoaders = require("./loaders");
const {
plugin: cacheControlPluginFork,
} = require("./lib/apollo-cache-control-fork");
const rootTypeDefs = gql`
enum CacheScope {
@ -12,6 +15,7 @@ const rootTypeDefs = gql`
}
directive @cacheControl(
maxAge: Int
staleWhileRevalidate: Int
scope: CacheScope
) on FIELD_DEFINITION | OBJECT
@ -51,7 +55,7 @@ const schema = makeExecutableSchema(
])
);
const plugins = [];
const plugins = [cacheControlPluginFork({ calculateHttpHeaders: true })];
if (process.env["NODE_ENV"] !== "test") {
plugins.push(beelinePlugin);
@ -76,6 +80,9 @@ const config = {
plugins,
// We use our own fork of the cacheControl plugin!
cacheControl: false,
// Enable Playground in production :)
introspection: true,
playground: {

View file

@ -0,0 +1,331 @@
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;
// FORK: Added this directive field!
staleWhileRevalidate?: number;
}
export enum CacheScope {
Public = 'PUBLIC',
Private = 'PRIVATE',
}
export interface CacheControlExtensionOptions {
defaultMaxAge?: number;
// FIXME: 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
) {
if (overallCachePolicy.staleWhileRevalidate) { // FORK
response.http.headers.set(
'Cache-Control',
`max-age=${
overallCachePolicy.maxAge
}, stale-while-revalidate=${
overallCachePolicy.staleWhileRevalidate
}, ${overallCachePolicy.scope.toLowerCase()}`,
);
} else {
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 staleWhileRevalidateArgument = cacheControlDirective.arguments.find( // FORK
argument => argument.name.value === 'staleWhileRevalidate',
);
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,
staleWhileRevalidate:
staleWhileRevalidateArgument &&
staleWhileRevalidateArgument.value &&
staleWhileRevalidateArgument.value.kind === 'IntValue'
? parseInt(staleWhileRevalidateArgument.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,
staleWhileRevalidate: otherHint.staleWhileRevalidate !== undefined ? otherHint.staleWhileRevalidate : hint.staleWhileRevalidate, // FORK
scope: otherHint.scope || hint.scope,
};
}
function computeOverallCachePolicy(
hints: MapResponsePathHints,
): Required<CacheHint> | undefined {
let lowestMaxAge: number | undefined = undefined;
let lowestMaxAgePlusSWR: number | undefined = undefined; // FORK
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;
// FORK
//
// SWR is defined as the amount of time _after_ max-age when we should
// treat a resource as no longer fresh, but not _entirely_ stale.
//
// So, to merge, we want to know the time when our first resource becomes
// non-fresh, and then time when our first resource becomes too stale to
// serve at all. The first value is the min max-age across our hints, and
// the second value is the min max-age-plus-SWR across our hints. So,
// that sum is what we actually compute and compare when looping here -
// and then we subtract the final max-age off of the final
// max-age-plus-SWR to get the SWR for our HTTP header!
//
// If SWR is not specified, we treat it as 0: the resource can be served
// for zero additional seconds!
//
// Note that we skip processing staleWhileRevalidate if maxAge is not
// also specified. TODO: Type check this, and/or louder error?
const maxAgePlusSWR = hint.maxAge + (hint.staleWhileRevalidate || 0);
lowestMaxAgePlusSWR =
lowestMaxAgePlusSWR !== undefined
? Math.min(lowestMaxAgePlusSWR, maxAgePlusSWR)
: maxAgePlusSWR;
}
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 && lowestMaxAgePlusSWR // FORK
? {
maxAge: lowestMaxAge,
staleWhileRevalidate: lowestMaxAgePlusSWR - lowestMaxAge, // FORK
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,
};

View file

@ -1,8 +1,8 @@
const { gql } = require("apollo-server");
const { getRestrictedZoneIds, oneWeek, oneHour } = require("../util");
const { getRestrictedZoneIds, oneWeek, oneDay, oneHour } = require("../util");
const typeDefs = gql`
type Item @cacheControl(maxAge: ${oneWeek}) {
type Item @cacheControl(maxAge: ${oneDay}, staleWhileRevalidate: ${oneWeek}) {
id: ID!
name: String!
description: String!
@ -32,7 +32,7 @@ const typeDefs = gql`
# How this item appears on the given species/color combo. If it does not
# fit the pet, we'll return an empty ItemAppearance with no layers.
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance! @cacheControl(maxAge: ${oneHour})
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance! @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneDay})
# This is set manually by Support users, when the pet is only for e.g.
# Maraquan pets, and our usual auto-detection isn't working. We provide
@ -53,14 +53,14 @@ const typeDefs = gql`
# NOTE: Most color IDs won't be accepted here. Either pass the ID of a
# major special color like Baby (#6), or leave it blank for standard
# bodies like Blue, Green, Red, etc.
speciesThatNeedModels(colorId: ID): [Species!]! @cacheControl(maxAge: ${oneHour})
speciesThatNeedModels(colorId: ID): [Species!]! @cacheControl(maxAge: 1)
# Return a single ItemAppearance for this item. It'll be for the species
# with the smallest ID for which we have item appearance data. We use this
# on the item page, to initialize the preview section. (You can find out
# which species this is for by going through the body field on
# ItemAppearance!)
canonicalAppearance: ItemAppearance @cacheControl(maxAge: ${oneHour})
canonicalAppearance: ItemAppearance @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneWeek})
# All zones that this item occupies, for at least one body. That is, it's
# a union of zones for all of its appearances! We use this for overview
@ -69,7 +69,7 @@ const typeDefs = gql`
# All bodies that this item is compatible with. Note that this might return
# the special representsAllPets body, e.g. if this is just a Background!
compatibleBodies: [Body!]! @cacheControl(maxAge: ${oneHour})
compatibleBodies: [Body!]! @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneWeek})
}
type ItemAppearance {
@ -134,8 +134,8 @@ const typeDefs = gql`
limit: Int
): ItemSearchResult!
# Get the 20 items most recently added to our database. Cache for 1 hour.
newestItems: [Item!]! @cacheControl(maxAge: 3600)
# Get the 20 items most recently added to our database.
newestItems: [Item!]! @cacheControl(maxAge: ${oneHour}, staleWhileRevalidate: ${oneDay})
# Get items that need models for the given color.
#

View file

@ -4,6 +4,8 @@ const {
getPoseFromPetState,
getRestrictedZoneIds,
oneWeek,
oneDay,
oneHour,
} = require("../util");
const typeDefs = gql`
@ -37,7 +39,7 @@ const typeDefs = gql`
UNKNOWN # for when we have the data, but we don't know what it is
}
type Body @cacheControl(maxAge: ${oneWeek}) {
type Body @cacheControl(maxAge: ${oneDay}, staleWhileRevalidate: ${oneWeek}) {
id: ID!
species: Species!
@ -48,7 +50,7 @@ const typeDefs = gql`
representsAllBodies: Boolean!
}
type PetAppearance @cacheControl(maxAge: ${oneWeek}) {
type PetAppearance @cacheControl(maxAge: ${oneHour}, staleWhileRevalidate: ${oneWeek}) {
id: ID!
species: Species!
color: Color!
@ -67,14 +69,14 @@ const typeDefs = gql`
extend type Query {
color(id: ID!): Color
allColors: [Color!]! @cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!)
allColors: [Color!]! @cacheControl(maxAge: ${oneHour}, staleWhileRevalidate: ${oneWeek})
species(id: ID!): Species
allSpecies: [Species!]! @cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!)
petAppearanceById(id: ID!): PetAppearance @cacheControl(maxAge: 10800) # Cache for 3 hours (Support might edit!)
allSpecies: [Species!]! @cacheControl(maxAge: ${oneDay}, staleWhileRevalidate: ${oneWeek})
petAppearanceById(id: ID!): PetAppearance @cacheControl(maxAge: 0) # Only Support really uses this, show changes fast
# The canonical pet appearance for the given species, color, and pose.
# Null if we don't have any data for this combination.
petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance
@cacheControl(maxAge: 10800) # Cache for 3 hours (we might model more!)
@cacheControl(maxAge: 1, staleWhileRevalidate: ${oneDay})
# All pet appearances we've ever seen for the given species and color. Note
# that this might include multiple copies for the same pose, and they might
# even be glitched data. We use this for Support tools, and we don't cache

View file

@ -153,4 +153,5 @@ module.exports = {
oneWeek: 604800,
oneDay: 86400,
oneHour: 3600,
oneMinute: 60,
};

230
yarn.lock
View file

@ -52,6 +52,13 @@
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc"
integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==
"@apollographql/graphql-playground-html@1.6.26":
version "1.6.26"
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz#2f7b610392e2a872722912fc342b43cf8d641cb3"
integrity sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==
dependencies:
xss "^1.0.6"
"@auth0/auth0-react@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.0.0.tgz#3eeced9557d54d4092032ecc071cd74052d09da7"
@ -3933,10 +3940,10 @@
"@types/keygrip" "*"
"@types/node" "*"
"@types/cors@^2.8.4":
version "2.8.6"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.6.tgz#cfaab33c49c15b1ded32f235111ce9123009bd02"
integrity sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==
"@types/cors@2.8.8":
version "2.8.8"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.8.tgz#317a8d8561995c60e35b9e0fcaa8d36660c98092"
integrity sha512-fO3gf3DxU2Trcbr75O7obVndW/X5k8rJNZkLXlQWStTHhP71PkRqjwPIEI0yMnJdg9R9OasjU+Bsr+Hr1xy/0w==
dependencies:
"@types/express" "*"
@ -3979,6 +3986,15 @@
"@types/node" "*"
"@types/range-parser" "*"
"@types/express-serve-static-core@4.17.17":
version "4.17.17"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.17.tgz#6ba02465165b6c9c3d8db3a28def6b16fc9b70f5"
integrity sha512-YYlVaCni5dnHc+bLZfY908IG1+x5xuibKZMGv8srKkvtul3wUuanYvpIj9GXXoWkQbaAdR+kgX46IETKUALWNQ==
dependencies:
"@types/node" "*"
"@types/qs" "*"
"@types/range-parser" "*"
"@types/express-unless@*":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.1.tgz#4f440b905e42bbf53382b8207bc337dc5ff9fd1f"
@ -3996,13 +4012,14 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/express@4.17.3":
version "4.17.3"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9"
integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==
"@types/express@4.17.7":
version "4.17.7"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59"
integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
"@types/qs" "*"
"@types/serve-static" "*"
"@types/fs-capacitor@*":
@ -4155,6 +4172,14 @@
"@types/node" "*"
form-data "^3.0.0"
"@types/node-fetch@2.5.7":
version "2.5.7"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "13.9.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349"
@ -4339,6 +4364,13 @@
dependencies:
"@types/node" "*"
"@types/ws@^7.0.0":
version "7.4.0"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.0.tgz#499690ea08736e05a8186113dac37769ab251a0e"
integrity sha512-Y29uQ3Uy+58bZrFLhX36hcI3Np37nqWE7ky5tjiDoy1GDZnIwVxS0CgF+s+1bXMzjKBFy+fqaRfb708iNzdinw==
dependencies:
"@types/node" "*"
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@ -4892,6 +4924,14 @@ anymatch@^3.0.3, anymatch@~3.1.1:
normalize-path "^3.0.0"
picomatch "^2.0.4"
apollo-cache-control@^0.11.6:
version "0.11.6"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.11.6.tgz#f7bdf924272af47ac474cf3f3f35cfc038cc9485"
integrity sha512-YZ+uuIG+fPy+mkpBS2qKF0v1qlzZ3PW6xZVaDukeK3ed3iAs4L/2YnkTqau3OmoF/VPzX2FmSkocX/OVd59YSw==
dependencies:
apollo-server-env "^3.0.0"
apollo-server-plugin-base "^0.10.4"
apollo-cache-control@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.9.1.tgz#2af4c53ef697a87808285a25f2b93ca0f183880c"
@ -4908,6 +4948,14 @@ apollo-datasource@^0.7.0:
apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3"
apollo-datasource@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.7.3.tgz#c824eb1457bdee5a3173ced0e35e594547e687a0"
integrity sha512-PE0ucdZYjHjUyXrFWRwT02yLcx2DACsZ0jm1Mp/0m/I9nZu/fEkvJxfsryXB6JndpmQO77gQHixf/xGCN976kA==
dependencies:
apollo-server-caching "^0.5.3"
apollo-server-env "^3.0.0"
apollo-engine-reporting-protobuf@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.4.tgz#73a064f8c9f2d6605192d1673729c66ec47d9cb7"
@ -4939,6 +4987,16 @@ apollo-env@^0.6.4:
node-fetch "^2.2.0"
sha.js "^2.4.11"
apollo-env@^0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.6.5.tgz#5a36e699d39e2356381f7203493187260fded9f3"
integrity sha512-jeBUVsGymeTHYWp3me0R2CZRZrFeuSZeICZHCeRflHTfnQtlmbSXdy5E0pOyRM9CU4JfQkKDC98S1YglQj7Bzg==
dependencies:
"@types/node-fetch" "2.5.7"
core-js "^3.0.1"
node-fetch "^2.2.0"
sha.js "^2.4.11"
apollo-graphql@^0.4.0:
version "0.4.3"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.4.3.tgz#cd25641d4151c931743cc1870a9d4be9f42b556f"
@ -4947,6 +5005,14 @@ apollo-graphql@^0.4.0:
apollo-env "^0.6.4"
lodash.sortby "^4.7.0"
apollo-graphql@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.6.0.tgz#37bee7dc853213269137f4c60bfdf2ee28658669"
integrity sha512-BxTf5LOQe649e9BNTPdyCGItVv4Ll8wZ2BKnmiYpRAocYEXAVrQPWuSr3dO4iipqAU8X0gvle/Xu9mSqg5b7Qg==
dependencies:
apollo-env "^0.6.5"
lodash.sortby "^4.7.0"
apollo-link-persisted-queries@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/apollo-link-persisted-queries/-/apollo-link-persisted-queries-0.2.2.tgz#156597cb259b7bb56cf4e967a7be0312954f4591"
@ -4965,6 +5031,13 @@ apollo-link@^1.2.1, apollo-link@^1.2.14:
tslib "^1.9.3"
zen-observable-ts "^0.8.21"
apollo-reporting-protobuf@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.6.2.tgz#5572866be9b77f133916532b10e15fbaa4158304"
integrity sha512-WJTJxLM+MRHNUxt1RTl4zD0HrLdH44F2mDzMweBj1yHL0kSt8I1WwoiF/wiGVSpnG48LZrBegCaOJeuVbJTbtw==
dependencies:
"@apollo/protobufjs" "^1.0.3"
apollo-server-caching@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.1.tgz#5cd0536ad5473abb667cc82b59bc56b96fb35db6"
@ -4972,6 +5045,13 @@ apollo-server-caching@^0.5.1:
dependencies:
lru-cache "^5.0.0"
apollo-server-caching@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.3.tgz#cf42a77ad09a46290a246810075eaa029b5305e1"
integrity sha512-iMi3087iphDAI0U2iSBE9qtx9kQoMMEWr6w+LwXruBD95ek9DWyj7OeC2U/ngLjRsXM43DoBDXlu7R+uMjahrQ==
dependencies:
lru-cache "^6.0.0"
apollo-server-core@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.12.0.tgz#980f29788c17f029e7248d331bee1ad2e1f07e5b"
@ -5000,6 +5080,38 @@ apollo-server-core@^2.12.0:
subscriptions-transport-ws "^0.9.11"
ws "^6.0.0"
apollo-server-core@^2.19.2:
version "2.19.2"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.19.2.tgz#9a5651be233aceab6b062e82eef4c8072f6ac230"
integrity sha512-liLgLhTIGWZtdQbxuxo3/Yv8j+faKQcI60kOL+uwfByGhoKLZEQp5nqi2IdMK6JXt1VuyKwKu7lTzj02a9S3jA==
dependencies:
"@apollographql/apollo-tools" "^0.4.3"
"@apollographql/graphql-playground-html" "1.6.26"
"@types/graphql-upload" "^8.0.0"
"@types/ws" "^7.0.0"
apollo-cache-control "^0.11.6"
apollo-datasource "^0.7.3"
apollo-graphql "^0.6.0"
apollo-reporting-protobuf "^0.6.2"
apollo-server-caching "^0.5.3"
apollo-server-env "^3.0.0"
apollo-server-errors "^2.4.2"
apollo-server-plugin-base "^0.10.4"
apollo-server-types "^0.6.3"
apollo-tracing "^0.12.2"
async-retry "^1.2.1"
fast-json-stable-stringify "^2.0.0"
graphql-extensions "^0.12.8"
graphql-tag "^2.11.0"
graphql-tools "^4.0.0"
graphql-upload "^8.0.2"
loglevel "^1.6.7"
lru-cache "^6.0.0"
sha.js "^2.4.11"
subscriptions-transport-ws "^0.9.11"
uuid "^8.0.0"
ws "^6.0.0"
apollo-server-env@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-2.4.3.tgz#9bceedaae07eafb96becdfd478f8d92617d825d2"
@ -5008,24 +5120,38 @@ apollo-server-env@^2.4.3:
node-fetch "^2.1.2"
util.promisify "^1.0.0"
apollo-server-env@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-3.0.0.tgz#0157c51f52b63aee39af190760acf789ffc744d9"
integrity sha512-tPSN+VttnPsoQAl/SBVUpGbLA97MXG990XIwq6YUnJyAixrrsjW1xYG7RlaOqetxm80y5mBZKLrRDiiSsW/vog==
dependencies:
node-fetch "^2.1.2"
util.promisify "^1.0.0"
apollo-server-errors@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.4.1.tgz#16ad49de6c9134bfb2b7dede9842e73bb239dbe2"
integrity sha512-7oEd6pUxqyWYUbQ9TA8tM0NU/3aGtXSEibo6+txUkuHe7QaxfZ2wHRp+pfT1LC1K3RXYjKj61/C2xEO19s3Kdg==
apollo-server-express@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.12.0.tgz#a03cef8a3aa753bff73156e6a31fd59a076dc48b"
integrity sha512-oTBKM2SsziCoFW+ta+ubJ/ypvsc+EWrbJnyZhJ5FBYzSXPstt/jvgZHgMO+kOQgHEHrbJwugNDUuLMSm608L7A==
apollo-server-errors@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz#1128738a1d14da989f58420896d70524784eabe5"
integrity sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ==
apollo-server-express@^2.19.2:
version "2.19.2"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.19.2.tgz#8b185e8fb3b71cde09ca3d5a5d5af340b0421e7b"
integrity sha512-1v2H6BgDkS4QzRbJ9djn2o0yv5m/filbpiupxAsCG9f+sAoSlY3eYSj84Sbex2r5+4itAvT9y84WI7d9RBYs/Q==
dependencies:
"@apollographql/graphql-playground-html" "1.6.24"
"@apollographql/graphql-playground-html" "1.6.26"
"@types/accepts" "^1.3.5"
"@types/body-parser" "1.19.0"
"@types/cors" "^2.8.4"
"@types/express" "4.17.3"
"@types/cors" "2.8.8"
"@types/express" "4.17.7"
"@types/express-serve-static-core" "4.17.17"
accepts "^1.3.5"
apollo-server-core "^2.12.0"
apollo-server-types "^0.3.1"
apollo-server-core "^2.19.2"
apollo-server-types "^0.6.3"
body-parser "^1.18.3"
cors "^2.8.4"
express "^4.17.1"
@ -5035,6 +5161,13 @@ apollo-server-express@^2.12.0:
subscriptions-transport-ws "^0.9.16"
type-is "^1.6.16"
apollo-server-plugin-base@^0.10.4:
version "0.10.4"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.10.4.tgz#fbf73f64f95537ca9f9639dd7c535eb5eeb95dcd"
integrity sha512-HRhbyHgHFTLP0ImubQObYhSgpmVH4Rk1BinnceZmwudIVLKrqayIVOELdyext/QnSmmzg5W7vF3NLGBcVGMqDg==
dependencies:
apollo-server-types "^0.6.3"
apollo-server-plugin-base@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.7.1.tgz#998d035723c5993171fd5aff3dbe7d2661db1a96"
@ -5058,17 +5191,34 @@ apollo-server-types@^0.3.1:
apollo-server-caching "^0.5.1"
apollo-server-env "^2.4.3"
apollo-server@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.12.0.tgz#24589754da7a5a9bf29d21bd084bc37f8b50f147"
integrity sha512-ETL2+/m2KSxCnVoSjE2bOWh6HV4i1RRQMu+vBDgNzZGSVqzxyWiHhCMoSx22/ow2ZchiBMTKsRN5wZxbahyEkg==
apollo-server-types@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.6.3.tgz#f7aa25ff7157863264d01a77d7934aa6e13399e8"
integrity sha512-aVR7SlSGGY41E1f11YYz5bvwA89uGmkVUtzMiklDhZ7IgRJhysT5Dflt5IuwDxp+NdQkIhVCErUXakopocFLAg==
dependencies:
apollo-server-core "^2.12.0"
apollo-server-express "^2.12.0"
apollo-reporting-protobuf "^0.6.2"
apollo-server-caching "^0.5.3"
apollo-server-env "^3.0.0"
apollo-server@^2.19.2:
version "2.19.2"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.19.2.tgz#af5dce17695801e0edd561328b665866e26104da"
integrity sha512-fyl8U2O1haBOvaF3Z4+ZNj2Z9KXtw0Hb13NG2+J7vyHTdDL/hEwX9bp9AnWlfXOYL8s/VeankAUNqw8kggBeZw==
dependencies:
apollo-server-core "^2.19.2"
apollo-server-express "^2.19.2"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
apollo-tracing@^0.12.2:
version "0.12.2"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.12.2.tgz#a261c3970bb421b6dadf50cd85d75b2567a7e52c"
integrity sha512-SYN4o0C0wR1fyS3+P0FthyvsQVHFopdmN3IU64IaspR/RZScPxZ3Ae8uu++fTvkQflAkglnFM0aX6DkZERBp6w==
dependencies:
apollo-server-env "^3.0.0"
apollo-server-plugin-base "^0.10.4"
apollo-tracing@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.9.1.tgz#c481be727550c73fda53d20d798cbe4ad67c61bd"
@ -6376,7 +6526,7 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.20.0:
commander@^2.20.0, commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -6839,6 +6989,11 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
cssfilter@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=
cssnano-preset-default@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76"
@ -8862,6 +9017,15 @@ graphql-extensions@^0.11.1:
apollo-server-env "^2.4.3"
apollo-server-types "^0.3.1"
graphql-extensions@^0.12.8:
version "0.12.8"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.12.8.tgz#9cdc2c43d8fe5e0f6c3177a004ac011da2a8aa0f"
integrity sha512-xjsSaB6yKt9jarFNNdivl2VOx52WySYhxPgf8Y16g6GKZyAzBoIFiwyGw5PJDlOSUa6cpmzn6o7z8fVMbSAbkg==
dependencies:
"@apollographql/apollo-tools" "^0.4.3"
apollo-server-env "^3.0.0"
apollo-server-types "^0.6.3"
graphql-subscriptions@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11"
@ -8907,10 +9071,10 @@ graphql@^14.5.3:
dependencies:
iterall "^1.2.2"
graphql@^15.0.0:
version "15.0.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.0.0.tgz#042a5eb5e2506a2e2111ce41eb446a8e570b8be9"
integrity sha512-ZyVO1xIF9F+4cxfkdhOJINM+51B06Friuv4M66W7HzUOeFd+vNzUn4vtswYINPi6sysjf1M2Ri/rwZALqgwbaQ==
graphql@^15.5.0:
version "15.5.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==
growly@^1.3.0:
version "1.3.0"
@ -16006,7 +16170,7 @@ uuid@^3.1.0, uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.0:
uuid@^8.0.0, uuid@^8.3.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
@ -16601,6 +16765,14 @@ xregexp@2.0.0:
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=
xss@^1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.8.tgz#32feb87feb74b3dcd3d404b7a68ababf10700535"
integrity sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw==
dependencies:
commander "^2.20.3"
cssfilter "0.0.10"
xtend@^4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"