From 8224051a785aa025de71a611b709187cdd287ee7 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 23 Jun 2023 17:17:11 +0200 Subject: [PATCH 1/3] feat(plugin-response-cache): add @cacheControl compatibility --- .../__tests__/response-cache.spec.ts | 65 +++++++++++++++ packages/plugins/response-cache/package.json | 2 +- pnpm-lock.yaml | 79 +++++++++++++++---- .../pages/docs/features/response-caching.mdx | 27 +++++++ 4 files changed, 155 insertions(+), 18 deletions(-) diff --git a/packages/plugins/response-cache/__tests__/response-cache.spec.ts b/packages/plugins/response-cache/__tests__/response-cache.spec.ts index 05dbc98671..4db849bb62 100644 --- a/packages/plugins/response-cache/__tests__/response-cache.spec.ts +++ b/packages/plugins/response-cache/__tests__/response-cache.spec.ts @@ -1,3 +1,4 @@ +import { cacheControlDirective } from '@envelop/response-cache' import { useResponseCache } from '@graphql-yoga/plugin-response-cache' import { createSchema, createYoga } from 'graphql-yoga' @@ -326,3 +327,67 @@ it('should skip response caching with `enabled` option', async () => { }, }) }) + +it('should work with @cacheControl directive', async () => { + const yoga = createYoga({ + schema: createSchema({ + typeDefs: /* GraphQL */ ` + ${cacheControlDirective} + + type Query @cacheControl(maxAge: 0) { + _: String + } + `, + resolvers: { + Query: { + _: () => 'DUMMY', + }, + }, + }), + plugins: [ + useResponseCache({ + session: () => null, + includeExtensionMetadata: true, + }), + ], + }) + function fetch() { + return yoga.fetch('http://localhost:3000/graphql', { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ query: '{__typename}' }), + }) + } + + let response = await fetch() + + expect(response.status).toEqual(200) + let body = await response.json() + expect(body).toEqual({ + data: { + __typename: 'Query', + }, + extensions: { + responseCache: { + didCache: false, + hit: false, + }, + }, + }) + + response = await fetch() + expect(response.status).toEqual(200) + body = await response.json() + expect(body).toMatchObject({ + data: { + __typename: 'Query', + }, + extensions: { + responseCache: { + hit: false, + }, + }, + }) +}) diff --git a/packages/plugins/response-cache/package.json b/packages/plugins/response-cache/package.json index b79cd8eb99..bf12fe23f1 100644 --- a/packages/plugins/response-cache/package.json +++ b/packages/plugins/response-cache/package.json @@ -52,7 +52,7 @@ "access": "public" }, "dependencies": { - "@envelop/response-cache": "^5.0.0" + "@envelop/response-cache": "^5.1.0" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a9ae330cd..390f29c714 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1438,7 +1438,7 @@ importers: version: 1.0.0(@apollo/client@3.7.15)(graphql@16.6.0) '@graphql-tools/executor-http': specifier: ^1.0.0 - version: 1.0.0(@types/node@18.16.16)(graphql@16.6.0) + version: 1.0.0(graphql@16.6.0) graphql: specifier: ^15.2.0 || ^16.0.0 version: 16.6.0 @@ -1458,7 +1458,7 @@ importers: dependencies: '@graphql-tools/executor-http': specifier: ^1.0.0 - version: 1.0.0(@types/node@18.16.16)(graphql@16.6.0) + version: 1.0.0(graphql@16.6.0) '@graphql-tools/executor-urql-exchange': specifier: ^1.0.0 version: 1.0.0(@urql/core@4.0.10)(graphql@16.6.0)(wonka@6.3.2) @@ -1520,7 +1520,7 @@ importers: version: 0.8.4(graphql@16.6.0) '@graphql-tools/url-loader': specifier: 8.0.0 - version: 8.0.0(@types/node@18.16.16)(graphql@16.6.0) + version: 8.0.0(graphql@16.6.0) graphiql: specifier: 2.0.7 version: 2.0.7(@codemirror/language@0.20.2)(@types/react@18.2.8)(graphql@16.6.0)(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0) @@ -1810,7 +1810,7 @@ importers: devDependencies: '@graphql-tools/executor-http': specifier: ^1.0.0 - version: 1.0.0(@types/node@18.16.16)(graphql@16.6.0) + version: 1.0.0(graphql@16.6.0) '@whatwg-node/fetch': specifier: ^0.9.0 version: 0.9.0 @@ -1883,8 +1883,8 @@ importers: packages/plugins/response-cache: dependencies: '@envelop/response-cache': - specifier: ^5.0.0 - version: 5.0.0(@envelop/core@4.0.0)(graphql@16.6.0) + specifier: ^5.1.0 + version: 5.1.0(@envelop/core@4.0.0)(graphql@16.6.0) graphql-yoga: specifier: ^4.0.2 version: link:../../graphql-yoga/dist @@ -6602,8 +6602,8 @@ packages: tslib: 2.5.3 dev: false - /@envelop/response-cache@5.0.0(@envelop/core@4.0.0)(graphql@16.6.0): - resolution: {integrity: sha512-CVcH0D2Ht0Rr1UJhOPPN5LUK18/UaoBi1PtNlqusY2yI5yHbzztqVsg9mxQv6jHuIHE6FWAYzZJN38viPGyYuA==} + /@envelop/response-cache@5.1.0(@envelop/core@4.0.0)(graphql@16.6.0): + resolution: {integrity: sha512-G81MF9XCQdmyZWgTkZa09rBusXNXhvjPMDjP4cfOpr8g1DEvEsxskEGT08f22hvl9Fg+G2wlVXBsHPLEr798Lg==} engines: {node: '>=16.0.0'} peerDependencies: '@envelop/core': ^4.0.0 @@ -6611,10 +6611,10 @@ packages: dependencies: '@envelop/core': 4.0.0 '@graphql-tools/utils': 10.0.1(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.0 + '@whatwg-node/fetch': 0.9.7 fast-json-stable-stringify: 2.1.0 graphql: 16.6.0 - lru-cache: 9.1.1 + lru-cache: 10.0.0 tslib: 2.5.3 dev: false @@ -7696,7 +7696,7 @@ packages: dependencies: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/utils': 10.0.1(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.0 + '@whatwg-node/fetch': 0.9.7 graphql: 16.6.0 tslib: 2.5.3 transitivePeerDependencies: @@ -7806,6 +7806,25 @@ packages: - utf-8-validate /@graphql-tools/executor-http@1.0.0(@types/node@18.16.16)(graphql@16.6.0): + resolution: {integrity: sha512-7R9IWRN1Iszyayd4qgguITLLTmRUZ3wSS5umK0xwShB8mFQ5cSsVx6rewPhGIwGEenN6e9ahwcGX9ytuLlw55g==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-tools/utils': 10.0.1(graphql@16.6.0) + '@repeaterjs/repeater': 3.0.4 + '@whatwg-node/fetch': 0.9.7 + dset: 3.1.2 + extract-files: 11.0.0 + graphql: 16.6.0 + meros: 1.2.1(@types/node@18.16.16) + tslib: 2.5.3 + value-or-promise: 1.0.12 + transitivePeerDependencies: + - '@types/node' + dev: true + + /@graphql-tools/executor-http@1.0.0(graphql@16.6.0): resolution: {integrity: sha512-7R9IWRN1Iszyayd4qgguITLLTmRUZ3wSS5umK0xwShB8mFQ5cSsVx6rewPhGIwGEenN6e9ahwcGX9ytuLlw55g==} engines: {node: '>=16.0.0'} peerDependencies: @@ -7908,7 +7927,7 @@ packages: '@graphql-tools/executor-http': 1.0.0(@types/node@18.16.16)(graphql@16.6.0) '@graphql-tools/graphql-tag-pluck': 8.0.0(@babel/core@7.22.1)(graphql@16.6.0) '@graphql-tools/utils': 10.0.1(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.0 + '@whatwg-node/fetch': 0.9.7 graphql: 16.6.0 tslib: 2.5.3 value-or-promise: 1.0.12 @@ -8051,7 +8070,7 @@ packages: '@graphql-tools/utils': 10.0.1(graphql@16.6.0) '@types/js-yaml': 4.0.5 '@types/json-stable-stringify': 1.0.34 - '@whatwg-node/fetch': 0.9.0 + '@whatwg-node/fetch': 0.9.7 chalk: 4.1.2 debug: 4.3.4(supports-color@9.2.3) dotenv: 16.0.3 @@ -8150,6 +8169,34 @@ packages: - bufferutil - encoding - utf-8-validate + dev: true + + /@graphql-tools/url-loader@8.0.0(graphql@16.6.0): + resolution: {integrity: sha512-rPc9oDzMnycvz+X+wrN3PLrhMBQkG4+sd8EzaFN6dypcssiefgWKToXtRKI8HHK68n2xEq1PyrOpkjHFJB+GwA==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@ardatan/sync-fetch': 0.0.1 + '@graphql-tools/delegate': 10.0.0(graphql@16.6.0) + '@graphql-tools/executor-graphql-ws': 1.0.0(graphql@16.6.0) + '@graphql-tools/executor-http': 1.0.0(graphql@16.6.0) + '@graphql-tools/executor-legacy-ws': 1.0.0(graphql@16.6.0) + '@graphql-tools/utils': 10.0.1(graphql@16.6.0) + '@graphql-tools/wrap': 10.0.0(graphql@16.6.0) + '@types/ws': 8.5.4 + '@whatwg-node/fetch': 0.9.0 + graphql: 16.6.0 + isomorphic-ws: 5.0.0(ws@8.13.0) + tslib: 2.5.3 + value-or-promise: 1.0.12 + ws: 8.13.0 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - encoding + - utf-8-validate + dev: false /@graphql-tools/utils@10.0.0(graphql@16.6.0): resolution: {integrity: sha512-ndBPc6zgR+eGU/jHLpuojrs61kYN3Z89JyMLwK3GCRkPv4EQn9EOr1UWqF1JO0iM+/jAVHY0mvfUxyrFFN9DUQ==} @@ -13680,7 +13727,6 @@ packages: dependencies: '@whatwg-node/node-fetch': 0.4.6 urlpattern-polyfill: 9.0.0 - dev: false /@whatwg-node/node-fetch@0.3.6: resolution: {integrity: sha512-w9wKgDO4C95qnXZRwZTfCmLWqyRnooGjcIwG0wADWjw9/HN0p7dtvtgSvItZtUyNteEvgTrd8QojNEqV6DAGTA==} @@ -13711,7 +13757,6 @@ packages: fast-querystring: 1.1.1 fast-url-parser: 1.1.3 tslib: 2.5.3 - dev: false /@whatwg-node/server-plugin-cookies@1.0.0(@whatwg-node/server@0.9.0): resolution: {integrity: sha512-o0MqK6H4Yhxht+J+cgZIpEhdb4SID1grFWfOiMdni3csNHBKZ+MKAFhxS+6wAJsarHN7BEZ0QMu4PG09Uwhr2g==} @@ -19471,7 +19516,7 @@ packages: dependencies: '@ardatan/fast-json-stringify': 0.0.6(ajv-formats@2.1.1)(ajv@8.12.0) '@whatwg-node/cookie-store': 0.1.0 - '@whatwg-node/fetch': 0.9.0 + '@whatwg-node/fetch': 0.9.7 '@whatwg-node/server': 0.8.1 ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) @@ -24241,6 +24286,7 @@ packages: /lru-cache@9.1.1: resolution: {integrity: sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==} engines: {node: 14 || >=16.14} + dev: true /lru-memoizer@2.2.0: resolution: {integrity: sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==} @@ -32234,7 +32280,6 @@ packages: /urlpattern-polyfill@9.0.0: resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} - dev: false /use-callback-ref@1.3.0(@types/react@18.2.8)(react@18.2.0): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} diff --git a/website/src/pages/docs/features/response-caching.mdx b/website/src/pages/docs/features/response-caching.mdx index b4edfaeff6..6b1addcc5e 100644 --- a/website/src/pages/docs/features/response-caching.mdx +++ b/website/src/pages/docs/features/response-caching.mdx @@ -96,6 +96,8 @@ Either globally, based on [schema coordinates](https://github.com/graphql/graphq If a query operation result contains multiple objects of the same or different types, the lowest TTL is picked. +### Using plugin configuration + ```ts filename="Response Cache configuration with TTL" useResponseCache({ session: () => null, @@ -112,6 +114,31 @@ useResponseCache({ }) ``` +### Using Schema directive directive `@cacheControl` + +For a schema first approach, the `@cacheControl` directive can be used to define the TTL. + +Notice that the global default TTL can only be define in the plugin options `ttl` and not via the directive. + +```ts filenmae="Response Cache configuration with TTL using @cacheControl directive" +import { cacheControlDirective } from '@graphql-yoga/plugin-response-cache' + +const typeDefs = /* GraphQL */ ` + # the directive needs to be defined in the schema + ${cacheControlDirective} + + type Query { + # cache operations selecting Query.lazy for 10 seconds + lazy: Something @cacheControl(maxAge: 10000) + } + + # only cache query operations containing User for 500ms + type User @cacheControl(maxAge: 500) { + #... + } +` +``` + ## Invalidations via Mutation When executing a mutation operation the cached query results that contain type entities within the Mutation result will be automatically be invalidated. From 69d139cd0fdd414d12e3e1a0ddca1004e93175eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Jun 2023 15:29:35 +0000 Subject: [PATCH 2/3] chore(dependencies): updated changesets for modified dependencies --- ...plugin-response-cache-2887-dependencies.md | 5 ++ pnpm-lock.yaml | 55 ++----------------- 2 files changed, 9 insertions(+), 51 deletions(-) create mode 100644 .changeset/@graphql-yoga_plugin-response-cache-2887-dependencies.md diff --git a/.changeset/@graphql-yoga_plugin-response-cache-2887-dependencies.md b/.changeset/@graphql-yoga_plugin-response-cache-2887-dependencies.md new file mode 100644 index 0000000000..71674635c7 --- /dev/null +++ b/.changeset/@graphql-yoga_plugin-response-cache-2887-dependencies.md @@ -0,0 +1,5 @@ +--- +'@graphql-yoga/plugin-response-cache': patch +--- +dependencies updates: + - Updated dependency [`@envelop/response-cache@^5.1.0` ↗︎](https://www.npmjs.com/package/@envelop/response-cache/v/5.1.0) (from `^5.0.0`, in `dependencies`) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 390f29c714..264083e29a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1438,7 +1438,7 @@ importers: version: 1.0.0(@apollo/client@3.7.15)(graphql@16.6.0) '@graphql-tools/executor-http': specifier: ^1.0.0 - version: 1.0.0(graphql@16.6.0) + version: 1.0.0(@types/node@18.16.16)(graphql@16.6.0) graphql: specifier: ^15.2.0 || ^16.0.0 version: 16.6.0 @@ -1458,7 +1458,7 @@ importers: dependencies: '@graphql-tools/executor-http': specifier: ^1.0.0 - version: 1.0.0(graphql@16.6.0) + version: 1.0.0(@types/node@18.16.16)(graphql@16.6.0) '@graphql-tools/executor-urql-exchange': specifier: ^1.0.0 version: 1.0.0(@urql/core@4.0.10)(graphql@16.6.0)(wonka@6.3.2) @@ -1520,7 +1520,7 @@ importers: version: 0.8.4(graphql@16.6.0) '@graphql-tools/url-loader': specifier: 8.0.0 - version: 8.0.0(graphql@16.6.0) + version: 8.0.0(@types/node@18.16.16)(graphql@16.6.0) graphiql: specifier: 2.0.7 version: 2.0.7(@codemirror/language@0.20.2)(@types/react@18.2.8)(graphql@16.6.0)(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0) @@ -1810,7 +1810,7 @@ importers: devDependencies: '@graphql-tools/executor-http': specifier: ^1.0.0 - version: 1.0.0(graphql@16.6.0) + version: 1.0.0(@types/node@18.16.16)(graphql@16.6.0) '@whatwg-node/fetch': specifier: ^0.9.0 version: 0.9.0 @@ -7806,25 +7806,6 @@ packages: - utf-8-validate /@graphql-tools/executor-http@1.0.0(@types/node@18.16.16)(graphql@16.6.0): - resolution: {integrity: sha512-7R9IWRN1Iszyayd4qgguITLLTmRUZ3wSS5umK0xwShB8mFQ5cSsVx6rewPhGIwGEenN6e9ahwcGX9ytuLlw55g==} - engines: {node: '>=16.0.0'} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - dependencies: - '@graphql-tools/utils': 10.0.1(graphql@16.6.0) - '@repeaterjs/repeater': 3.0.4 - '@whatwg-node/fetch': 0.9.7 - dset: 3.1.2 - extract-files: 11.0.0 - graphql: 16.6.0 - meros: 1.2.1(@types/node@18.16.16) - tslib: 2.5.3 - value-or-promise: 1.0.12 - transitivePeerDependencies: - - '@types/node' - dev: true - - /@graphql-tools/executor-http@1.0.0(graphql@16.6.0): resolution: {integrity: sha512-7R9IWRN1Iszyayd4qgguITLLTmRUZ3wSS5umK0xwShB8mFQ5cSsVx6rewPhGIwGEenN6e9ahwcGX9ytuLlw55g==} engines: {node: '>=16.0.0'} peerDependencies: @@ -8169,34 +8150,6 @@ packages: - bufferutil - encoding - utf-8-validate - dev: true - - /@graphql-tools/url-loader@8.0.0(graphql@16.6.0): - resolution: {integrity: sha512-rPc9oDzMnycvz+X+wrN3PLrhMBQkG4+sd8EzaFN6dypcssiefgWKToXtRKI8HHK68n2xEq1PyrOpkjHFJB+GwA==} - engines: {node: '>=16.0.0'} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - dependencies: - '@ardatan/sync-fetch': 0.0.1 - '@graphql-tools/delegate': 10.0.0(graphql@16.6.0) - '@graphql-tools/executor-graphql-ws': 1.0.0(graphql@16.6.0) - '@graphql-tools/executor-http': 1.0.0(graphql@16.6.0) - '@graphql-tools/executor-legacy-ws': 1.0.0(graphql@16.6.0) - '@graphql-tools/utils': 10.0.1(graphql@16.6.0) - '@graphql-tools/wrap': 10.0.0(graphql@16.6.0) - '@types/ws': 8.5.4 - '@whatwg-node/fetch': 0.9.0 - graphql: 16.6.0 - isomorphic-ws: 5.0.0(ws@8.13.0) - tslib: 2.5.3 - value-or-promise: 1.0.12 - ws: 8.13.0 - transitivePeerDependencies: - - '@types/node' - - bufferutil - - encoding - - utf-8-validate - dev: false /@graphql-tools/utils@10.0.0(graphql@16.6.0): resolution: {integrity: sha512-ndBPc6zgR+eGU/jHLpuojrs61kYN3Z89JyMLwK3GCRkPv4EQn9EOr1UWqF1JO0iM+/jAVHY0mvfUxyrFFN9DUQ==} From f87c2c0cd1f1353dbc38bd7b8f0bb6982c8bdc43 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 23 Jun 2023 18:39:56 +0200 Subject: [PATCH 3/3] add changeset --- .changeset/orange-grapes-exist.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-grapes-exist.md diff --git a/.changeset/orange-grapes-exist.md b/.changeset/orange-grapes-exist.md new file mode 100644 index 0000000000..b26c94cc0e --- /dev/null +++ b/.changeset/orange-grapes-exist.md @@ -0,0 +1,5 @@ +--- +'@graphql-yoga/plugin-response-cache': minor +--- + +Add @cacheControl directive to configure cache in a schema first approach