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/.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 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..264083e29a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: @@ -7908,7 +7908,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 +8051,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 @@ -13680,7 +13680,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 +13710,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 +19469,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 +24239,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 +32233,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.