1 | import { createInMemoryCache, defaultBuildResponseCacheKey, useResponseCache as useEnvelopResponseCache, } from '@envelop/response-cache';
|
2 | const operationIdByRequest = new WeakMap();
|
3 |
|
4 | function sessionFactoryForEnvelop({ request }) {
|
5 | return operationIdByRequest.get(request);
|
6 | }
|
7 | const cacheKeyFactoryForEnvelop = async function cacheKeyFactoryForEnvelop({ sessionId }) {
|
8 | if (sessionId == null) {
|
9 | throw new Error('[useResponseCache] This plugin is not configured correctly. Make sure you use this plugin with GraphQL Yoga');
|
10 | }
|
11 | return sessionId;
|
12 | };
|
13 | const getDocumentStringForEnvelop = (executionArgs) => {
|
14 | const context = executionArgs.contextValue;
|
15 | if (context.params?.query == null) {
|
16 | throw new Error('[useResponseCache] This plugin is not configured correctly. Make sure you use this plugin with GraphQL Yoga');
|
17 | }
|
18 | return context.params.query;
|
19 | };
|
20 | export function useResponseCache(options) {
|
21 | const buildResponseCacheKey = options?.buildResponseCacheKey || defaultBuildResponseCacheKey;
|
22 | const cache = options.cache ?? createInMemoryCache();
|
23 | const enabled = options.enabled ?? (() => true);
|
24 | let logger;
|
25 | return {
|
26 | onYogaInit({ yoga }) {
|
27 | logger = yoga.logger;
|
28 | },
|
29 | onPluginInit({ addPlugin }) {
|
30 | addPlugin(useEnvelopResponseCache({
|
31 | ...options,
|
32 | enabled({ request }) {
|
33 | return enabled(request);
|
34 | },
|
35 | cache,
|
36 | getDocumentString: getDocumentStringForEnvelop,
|
37 | session: sessionFactoryForEnvelop,
|
38 | buildResponseCacheKey: cacheKeyFactoryForEnvelop,
|
39 | shouldCacheResult({ cacheKey, result }) {
|
40 | const shouldCached = options.shouldCacheResult
|
41 | ? options.shouldCacheResult({ cacheKey, result })
|
42 | : !result.errors?.length;
|
43 | if (shouldCached) {
|
44 | const extensions = (result.extensions || (result.extensions = {}));
|
45 | const httpExtensions = (extensions.http || (extensions.http = {}));
|
46 | const headers = (httpExtensions.headers || (httpExtensions.headers = {}));
|
47 | headers['ETag'] = cacheKey;
|
48 | headers['Last-Modified'] = new Date().toUTCString();
|
49 | }
|
50 | else {
|
51 | logger.warn('[useResponseCache] Failed to cache due to errors');
|
52 | }
|
53 | return shouldCached;
|
54 | },
|
55 | }));
|
56 | },
|
57 | async onRequest({ request, fetchAPI, endResponse }) {
|
58 | if (enabled(request)) {
|
59 | const operationId = request.headers.get('If-None-Match');
|
60 | if (operationId) {
|
61 | const cachedResponse = await cache.get(operationId);
|
62 | if (cachedResponse) {
|
63 | const lastModifiedFromClient = request.headers.get('If-Modified-Since');
|
64 | const lastModifiedFromCache = cachedResponse.extensions?.http?.headers?.['Last-Modified'];
|
65 | if (
|
66 |
|
67 | lastModifiedFromCache != null &&
|
68 |
|
69 | (lastModifiedFromClient == null ||
|
70 | new Date(lastModifiedFromClient).getTime() >=
|
71 | new Date(lastModifiedFromCache).getTime())) {
|
72 | const okResponse = new fetchAPI.Response(null, {
|
73 | status: 304,
|
74 | headers: {
|
75 | ETag: operationId,
|
76 | },
|
77 | });
|
78 | endResponse(okResponse);
|
79 | }
|
80 | }
|
81 | }
|
82 | }
|
83 | },
|
84 | async onParams({ params, request, setResult }) {
|
85 | const operationId = await buildResponseCacheKey({
|
86 | documentString: params.query || '',
|
87 | variableValues: params.variables,
|
88 | operationName: params.operationName,
|
89 | sessionId: await options.session(request),
|
90 | });
|
91 | operationIdByRequest.set(request, operationId);
|
92 | if (enabled(request)) {
|
93 | const cachedResponse = await cache.get(operationId);
|
94 | if (cachedResponse) {
|
95 | if (options.includeExtensionMetadata) {
|
96 | setResult({
|
97 | ...cachedResponse,
|
98 | extensions: {
|
99 | ...cachedResponse.extensions,
|
100 | responseCache: {
|
101 | hit: true,
|
102 | },
|
103 | },
|
104 | });
|
105 | }
|
106 | else {
|
107 | setResult(cachedResponse);
|
108 | }
|
109 | return;
|
110 | }
|
111 | }
|
112 | },
|
113 | };
|
114 | }
|
115 | export { createInMemoryCache };
|