diff --git a/package.json b/package.json index 8ac37a7..1e873bb 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/server/index.js b/src/server/index.js index 9e71713..910864a 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -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: { diff --git a/src/server/lib/apollo-cache-control-fork.ts b/src/server/lib/apollo-cache-control-fork.ts new file mode 100644 index 0000000..c07971f --- /dev/null +++ b/src/server/lib/apollo-cache-control-fork.ts @@ -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 { + // Not readonly: plugins can set it. + overallCachePolicy?: Required | undefined; + } +} + +type MapResponsePathHints = Map; + +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 | 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 | 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, +}; diff --git a/src/server/types/Item.js b/src/server/types/Item.js index 468015d..6d0fc48 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -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. # diff --git a/src/server/types/PetAppearance.js b/src/server/types/PetAppearance.js index f47765c..9970390 100644 --- a/src/server/types/PetAppearance.js +++ b/src/server/types/PetAppearance.js @@ -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 diff --git a/src/server/util.js b/src/server/util.js index fbe3f42..35e9ef9 100644 --- a/src/server/util.js +++ b/src/server/util.js @@ -153,4 +153,5 @@ module.exports = { oneWeek: 604800, oneDay: 86400, oneHour: 3600, + oneMinute: 60, }; diff --git a/yarn.lock b/yarn.lock index d33b61b..f1b5f0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"