@envelop/response-cache 6.1.0 → 6.1.1-alpha-20231123130839-6a4e30b3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/cjs/plugin.js +19 -22
- package/esm/plugin.js +20 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -153,6 +153,60 @@ const getEnveloped = envelop({
|
|
|
153
153
|
> Note: In the Recipes below, be sure to provide your Redis `cache` instance with
|
|
154
154
|
> `useResponseCache({ cache })`.
|
|
155
155
|
|
|
156
|
+
### Cloudflare KV Cache
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
yarn add @envelop/response-cache-cloudflare-kv
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
In order to use the Cloudflare KV cache, you need to:
|
|
163
|
+
|
|
164
|
+
- Create a Cloudflare KV namespace
|
|
165
|
+
- Add that namespace to your `wrangler.toml` in order to access it from your worker. Read the
|
|
166
|
+
[KV docs](https://developers.cloudflare.com/kv/get-started/) to get started.
|
|
167
|
+
- Pass the KV namespace to the `createKvCache` function and set to the `useResponseCache` plugin
|
|
168
|
+
options. See the example below.
|
|
169
|
+
|
|
170
|
+
The example below demonstrates how to use this with graphql-yoga within a Cloudflare Worker script.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { createSchema, createYoga, YogaInitialContext } from 'graphql-yoga'
|
|
174
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
175
|
+
import { createKvCache } from '@envelop/response-cache-cloudflare-kv'
|
|
176
|
+
import { resolvers } from './graphql-schema/resolvers.generated'
|
|
177
|
+
import { typeDefs } from './graphql-schema/typeDefs.generated'
|
|
178
|
+
|
|
179
|
+
export type Env = {
|
|
180
|
+
GRAPHQL_RESPONSE_CACHE: KVNamespace
|
|
181
|
+
}
|
|
182
|
+
export type GraphQLContext = YogaInitialContext & Env & ExecutionContext
|
|
183
|
+
|
|
184
|
+
export default {
|
|
185
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
186
|
+
const cache = createKvCache({
|
|
187
|
+
KV: env.GRAPHQL_RESPONSE_CACHE,
|
|
188
|
+
ctx,
|
|
189
|
+
keyPrefix: 'graphql' // optional
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const graphqlServer = createYoga<GraphQLContext>({
|
|
193
|
+
schema: createSchema({ typeDefs, resolvers }),
|
|
194
|
+
plugins: [
|
|
195
|
+
useResponseCache({
|
|
196
|
+
cache,
|
|
197
|
+
session: () => null
|
|
198
|
+
})
|
|
199
|
+
]
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
return graphqlServer.fetch(request, env, ctx)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
> Note: In the Recipes below, be sure to provide your Cloudflare KV `cache` instance with
|
|
208
|
+
> `useResponseCache({ cache })`.
|
|
209
|
+
|
|
156
210
|
## Recipes
|
|
157
211
|
|
|
158
212
|
### Cache with maximum TTL
|
package/cjs/plugin.js
CHANGED
|
@@ -40,10 +40,9 @@ function defaultGetDocumentString(executionArgs) {
|
|
|
40
40
|
return (0, core_1.getDocumentString)(executionArgs.document, graphql_1.print);
|
|
41
41
|
}
|
|
42
42
|
exports.defaultGetDocumentString = defaultGetDocumentString;
|
|
43
|
-
const originalDocumentMap = new WeakMap();
|
|
44
43
|
const addEntityInfosToDocument = (0, utils_1.memoize4)(function addTypeNameToDocument(document, addTypeNameToDocumentOpts, schema, idFieldByTypeName) {
|
|
45
44
|
const typeInfo = new graphql_1.TypeInfo(schema);
|
|
46
|
-
|
|
45
|
+
return (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
|
|
47
46
|
OperationDefinition: {
|
|
48
47
|
enter(node) {
|
|
49
48
|
if (!addTypeNameToDocumentOpts.invalidateViaMutation && node.operation === 'mutation') {
|
|
@@ -85,8 +84,6 @@ const addEntityInfosToDocument = (0, utils_1.memoize4)(function addTypeNameToDoc
|
|
|
85
84
|
};
|
|
86
85
|
},
|
|
87
86
|
}));
|
|
88
|
-
originalDocumentMap.set(newDocument, document);
|
|
89
|
-
return newDocument;
|
|
90
87
|
});
|
|
91
88
|
function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache)(), ttl: globalTtl = Infinity, session, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, scopePerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = exports.defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = exports.defaultShouldCacheResult, includeExtensionMetadata = typeof process !== 'undefined'
|
|
92
89
|
? // eslint-disable-next-line dot-notation
|
|
@@ -94,7 +91,6 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
|
|
|
94
91
|
: false, }) {
|
|
95
92
|
const ignoredTypesMap = new Set(ignoredTypes);
|
|
96
93
|
const typePerSchemaCoordinateMap = new Map();
|
|
97
|
-
enabled = enabled ? (0, utils_1.memoize1)(enabled) : enabled;
|
|
98
94
|
// never cache Introspections
|
|
99
95
|
ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
|
|
100
96
|
const addTypeNameToDocumentOpts = { invalidateViaMutation };
|
|
@@ -107,17 +103,6 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
|
|
|
107
103
|
return Object.keys(data).some(fieldName => scopePerSchemaCoordinate[`${typeName}.${fieldName}`] === 'PRIVATE');
|
|
108
104
|
}
|
|
109
105
|
return {
|
|
110
|
-
onParse() {
|
|
111
|
-
return ({ result, replaceParseResult, context }) => {
|
|
112
|
-
if (enabled && !enabled(context)) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (!originalDocumentMap.has(result) && result.kind === graphql_1.Kind.DOCUMENT) {
|
|
116
|
-
const newDocument = addEntityInfosToDocument(result, addTypeNameToDocumentOpts, schema, idFieldByTypeName);
|
|
117
|
-
replaceParseResult(newDocument);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
106
|
onSchemaChange({ schema: newSchema }) {
|
|
122
107
|
if (schema === newSchema) {
|
|
123
108
|
return;
|
|
@@ -226,6 +211,10 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
|
|
|
226
211
|
if (invalidateViaMutation !== false) {
|
|
227
212
|
const operationAST = (0, graphql_1.getOperationAST)(onExecuteParams.args.document, onExecuteParams.args.operationName);
|
|
228
213
|
if (operationAST?.operation === 'mutation') {
|
|
214
|
+
onExecuteParams.setExecuteFn(args => onExecuteParams.executeFn({
|
|
215
|
+
...args,
|
|
216
|
+
document: addEntityInfosToDocument(args.document, addTypeNameToDocumentOpts, args.schema, idFieldByTypeName),
|
|
217
|
+
}));
|
|
229
218
|
return {
|
|
230
219
|
onExecuteDone({ result, setResult }) {
|
|
231
220
|
if ((0, core_1.isAsyncIterable)(result)) {
|
|
@@ -245,12 +234,14 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
|
|
|
245
234
|
});
|
|
246
235
|
const cachedResponse = (await cache.get(cacheKey));
|
|
247
236
|
if (cachedResponse != null) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
237
|
+
onExecuteParams.setExecuteFn(() => {
|
|
238
|
+
if (includeExtensionMetadata) {
|
|
239
|
+
return resultWithMetadata(cachedResponse, { hit: true });
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
return cachedResponse;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
254
245
|
return;
|
|
255
246
|
}
|
|
256
247
|
if (ttlPerSchemaCoordinate) {
|
|
@@ -283,6 +274,12 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
|
|
|
283
274
|
setResult(resultWithMetadata(result, { hit: false, didCache: true, ttl: finalTtl }));
|
|
284
275
|
}
|
|
285
276
|
}
|
|
277
|
+
onExecuteParams.setExecuteFn(args => {
|
|
278
|
+
return onExecuteParams.executeFn({
|
|
279
|
+
...args,
|
|
280
|
+
document: addEntityInfosToDocument(args.document, addTypeNameToDocumentOpts, args.schema, idFieldByTypeName),
|
|
281
|
+
});
|
|
282
|
+
});
|
|
286
283
|
return {
|
|
287
284
|
onExecuteDone({ result, setResult }) {
|
|
288
285
|
if ((0, core_1.isAsyncIterable)(result)) {
|
package/esm/plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import jsonStableStringify from 'fast-json-stable-stringify';
|
|
2
2
|
import { getOperationAST, Kind, print, TypeInfo, visit, visitWithTypeInfo, } from 'graphql';
|
|
3
3
|
import { getDocumentString, isAsyncIterable, } from '@envelop/core';
|
|
4
|
-
import { getDirective, MapperKind, mapSchema,
|
|
4
|
+
import { getDirective, MapperKind, mapSchema, memoize4, mergeIncrementalResult, } from '@graphql-tools/utils';
|
|
5
5
|
import { hashSHA256 } from './hash-sha256.js';
|
|
6
6
|
import { createInMemoryCache } from './in-memory-cache.js';
|
|
7
7
|
/**
|
|
@@ -33,10 +33,9 @@ export const defaultShouldCacheResult = (params) => {
|
|
|
33
33
|
export function defaultGetDocumentString(executionArgs) {
|
|
34
34
|
return getDocumentString(executionArgs.document, print);
|
|
35
35
|
}
|
|
36
|
-
const originalDocumentMap = new WeakMap();
|
|
37
36
|
const addEntityInfosToDocument = memoize4(function addTypeNameToDocument(document, addTypeNameToDocumentOpts, schema, idFieldByTypeName) {
|
|
38
37
|
const typeInfo = new TypeInfo(schema);
|
|
39
|
-
|
|
38
|
+
return visit(document, visitWithTypeInfo(typeInfo, {
|
|
40
39
|
OperationDefinition: {
|
|
41
40
|
enter(node) {
|
|
42
41
|
if (!addTypeNameToDocumentOpts.invalidateViaMutation && node.operation === 'mutation') {
|
|
@@ -78,8 +77,6 @@ const addEntityInfosToDocument = memoize4(function addTypeNameToDocument(documen
|
|
|
78
77
|
};
|
|
79
78
|
},
|
|
80
79
|
}));
|
|
81
|
-
originalDocumentMap.set(newDocument, document);
|
|
82
|
-
return newDocument;
|
|
83
80
|
});
|
|
84
81
|
export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, scopePerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = defaultShouldCacheResult, includeExtensionMetadata = typeof process !== 'undefined'
|
|
85
82
|
? // eslint-disable-next-line dot-notation
|
|
@@ -87,7 +84,6 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
|
|
|
87
84
|
: false, }) {
|
|
88
85
|
const ignoredTypesMap = new Set(ignoredTypes);
|
|
89
86
|
const typePerSchemaCoordinateMap = new Map();
|
|
90
|
-
enabled = enabled ? memoize1(enabled) : enabled;
|
|
91
87
|
// never cache Introspections
|
|
92
88
|
ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
|
|
93
89
|
const addTypeNameToDocumentOpts = { invalidateViaMutation };
|
|
@@ -100,17 +96,6 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
|
|
|
100
96
|
return Object.keys(data).some(fieldName => scopePerSchemaCoordinate[`${typeName}.${fieldName}`] === 'PRIVATE');
|
|
101
97
|
}
|
|
102
98
|
return {
|
|
103
|
-
onParse() {
|
|
104
|
-
return ({ result, replaceParseResult, context }) => {
|
|
105
|
-
if (enabled && !enabled(context)) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (!originalDocumentMap.has(result) && result.kind === Kind.DOCUMENT) {
|
|
109
|
-
const newDocument = addEntityInfosToDocument(result, addTypeNameToDocumentOpts, schema, idFieldByTypeName);
|
|
110
|
-
replaceParseResult(newDocument);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
},
|
|
114
99
|
onSchemaChange({ schema: newSchema }) {
|
|
115
100
|
if (schema === newSchema) {
|
|
116
101
|
return;
|
|
@@ -219,6 +204,10 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
|
|
|
219
204
|
if (invalidateViaMutation !== false) {
|
|
220
205
|
const operationAST = getOperationAST(onExecuteParams.args.document, onExecuteParams.args.operationName);
|
|
221
206
|
if (operationAST?.operation === 'mutation') {
|
|
207
|
+
onExecuteParams.setExecuteFn(args => onExecuteParams.executeFn({
|
|
208
|
+
...args,
|
|
209
|
+
document: addEntityInfosToDocument(args.document, addTypeNameToDocumentOpts, args.schema, idFieldByTypeName),
|
|
210
|
+
}));
|
|
222
211
|
return {
|
|
223
212
|
onExecuteDone({ result, setResult }) {
|
|
224
213
|
if (isAsyncIterable(result)) {
|
|
@@ -238,12 +227,14 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
|
|
|
238
227
|
});
|
|
239
228
|
const cachedResponse = (await cache.get(cacheKey));
|
|
240
229
|
if (cachedResponse != null) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
230
|
+
onExecuteParams.setExecuteFn(() => {
|
|
231
|
+
if (includeExtensionMetadata) {
|
|
232
|
+
return resultWithMetadata(cachedResponse, { hit: true });
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
return cachedResponse;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
247
238
|
return;
|
|
248
239
|
}
|
|
249
240
|
if (ttlPerSchemaCoordinate) {
|
|
@@ -276,6 +267,12 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
|
|
|
276
267
|
setResult(resultWithMetadata(result, { hit: false, didCache: true, ttl: finalTtl }));
|
|
277
268
|
}
|
|
278
269
|
}
|
|
270
|
+
onExecuteParams.setExecuteFn(args => {
|
|
271
|
+
return onExecuteParams.executeFn({
|
|
272
|
+
...args,
|
|
273
|
+
document: addEntityInfosToDocument(args.document, addTypeNameToDocumentOpts, args.schema, idFieldByTypeName),
|
|
274
|
+
});
|
|
275
|
+
});
|
|
279
276
|
return {
|
|
280
277
|
onExecuteDone({ result, setResult }) {
|
|
281
278
|
if (isAsyncIterable(result)) {
|