@envelop/response-cache 3.0.0-alpha-4ba70ab.0 → 3.0.0-alpha-d0d0776.0
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 +12 -0
- package/index.js +104 -113
- package/index.mjs +96 -108
- package/package.json +19 -7
package/README.md
CHANGED
|
@@ -8,6 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
[Check out the GraphQL Response Cache Guide for more information](https://envelop.dev/docs/guides/adding-a-graphql-response-cache)
|
|
10
10
|
|
|
11
|
+
> Watch [Episode #34 of `graphql.wtf`](https://graphql.wtf/episodes/34-response-cache-plugin-with-envelop) for a quick introduction to using Response Cache plugin with Envelop:
|
|
12
|
+
|
|
13
|
+
<iframe
|
|
14
|
+
width="100%"
|
|
15
|
+
height="400"
|
|
16
|
+
src="https://www.youtube.com/embed/1EBphPltkA4"
|
|
17
|
+
title="YouTube video player"
|
|
18
|
+
frameBorder="0"
|
|
19
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
20
|
+
allowFullScreen
|
|
21
|
+
></iframe>
|
|
22
|
+
|
|
11
23
|
## Getting Started
|
|
12
24
|
|
|
13
25
|
```bash
|
package/index.js
CHANGED
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const LRU = _interopDefault(require('lru-cache'));
|
|
5
|
+
const LRU = require('lru-cache');
|
|
8
6
|
const core = require('@envelop/core');
|
|
9
7
|
const utils = require('@graphql-tools/utils');
|
|
10
8
|
const graphql = require('graphql');
|
|
11
|
-
const jsonStableStringify =
|
|
9
|
+
const jsonStableStringify = require('fast-json-stable-stringify');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
+
|
|
13
|
+
const LRU__default = /*#__PURE__*/_interopDefaultLegacy(LRU);
|
|
14
|
+
const jsonStableStringify__default = /*#__PURE__*/_interopDefaultLegacy(jsonStableStringify);
|
|
12
15
|
|
|
13
16
|
const createInMemoryCache = (params) => {
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
max: params === null || params === void 0 ? void 0 : params.max,
|
|
17
|
+
const buildEntityId = params?.buildEntityId ?? defaultBuildEntityId;
|
|
18
|
+
const cachedResponses = new LRU__default["default"]({
|
|
19
|
+
max: params?.max,
|
|
18
20
|
stale: false,
|
|
19
21
|
noDisposeOnSet: true,
|
|
20
22
|
dispose(responseId) {
|
|
@@ -24,13 +26,12 @@ const createInMemoryCache = (params) => {
|
|
|
24
26
|
const entityToResponseIds = new Map();
|
|
25
27
|
const responseIdToEntityIds = new Map();
|
|
26
28
|
function purgeResponse(responseId, shouldRemove = true) {
|
|
27
|
-
var _a;
|
|
28
29
|
const entityIds = responseIdToEntityIds.get(responseId);
|
|
29
30
|
// get entities related to the response
|
|
30
31
|
if (entityIds !== undefined) {
|
|
31
32
|
for (const entityId of entityIds) {
|
|
32
33
|
// remove the response mapping from the entity
|
|
33
|
-
|
|
34
|
+
entityToResponseIds.get(entityId)?.delete(responseId);
|
|
34
35
|
}
|
|
35
36
|
// remove all the entity mappings from the response
|
|
36
37
|
responseIdToEntityIds.delete(responseId);
|
|
@@ -78,8 +79,7 @@ const createInMemoryCache = (params) => {
|
|
|
78
79
|
}
|
|
79
80
|
},
|
|
80
81
|
get(responseId) {
|
|
81
|
-
|
|
82
|
-
return (_a = cachedResponses.get(responseId)) !== null && _a !== void 0 ? _a : null;
|
|
82
|
+
return cachedResponses.get(responseId) ?? null;
|
|
83
83
|
},
|
|
84
84
|
invalidate(entitiesToRemove) {
|
|
85
85
|
for (const { typename, id } of entitiesToRemove) {
|
|
@@ -124,7 +124,7 @@ const hashSHA256 = async (text) => {
|
|
|
124
124
|
if (nodeCrypto) {
|
|
125
125
|
return nodeCrypto.then(crypto => crypto.createHash('sha256').update(text).digest('hex'));
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
if (webCrypto) {
|
|
128
128
|
let buf;
|
|
129
129
|
if (typeof TextEncoder !== 'undefined') {
|
|
130
130
|
buf = new TextEncoder().encode(text);
|
|
@@ -159,15 +159,12 @@ const rawDocumentStringSymbol = Symbol('rawDocumentString');
|
|
|
159
159
|
* Default function used for building the response cache key.
|
|
160
160
|
* It is exported here for advanced use-cases. E.g. if you want to short circuit and serve responses from the cache on a global level in order to completely by-pass the GraphQL flow.
|
|
161
161
|
*/
|
|
162
|
-
const defaultBuildResponseCacheKey = params =>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
(_c = params.sessionId) !== null && _c !== void 0 ? _c : '',
|
|
169
|
-
].join('|'));
|
|
170
|
-
};
|
|
162
|
+
const defaultBuildResponseCacheKey = params => hashSHA256([
|
|
163
|
+
params.documentString,
|
|
164
|
+
params.operationName ?? '',
|
|
165
|
+
jsonStableStringify__default["default"](params.variableValues ?? {}),
|
|
166
|
+
params.sessionId ?? '',
|
|
167
|
+
].join('|'));
|
|
171
168
|
/**
|
|
172
169
|
* Default function used to check if the result should be cached.
|
|
173
170
|
*
|
|
@@ -188,18 +185,22 @@ const defaultGetDocumentStringFromContext = context => context[rawDocumentString
|
|
|
188
185
|
function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentStringFromContext = defaultGetDocumentStringFromContext, shouldCacheResult = defaultShouldCacheResult,
|
|
189
186
|
// eslint-disable-next-line dot-notation
|
|
190
187
|
includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
|
|
188
|
+
const appliedTransform = Symbol('responseCache.appliedTransform');
|
|
191
189
|
const ignoredTypesMap = new Set(ignoredTypes);
|
|
192
|
-
const schemaCache = new WeakMap();
|
|
193
190
|
// never cache Introspections
|
|
194
191
|
ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
|
|
195
192
|
return {
|
|
196
|
-
onSchemaChange(
|
|
197
|
-
|
|
198
|
-
if (schema
|
|
199
|
-
|
|
200
|
-
schemaCache.set(ctx.schema, schema);
|
|
193
|
+
onSchemaChange({ schema, replaceSchema }) {
|
|
194
|
+
// @ts-expect-error See https://github.com/graphql/graphql-js/pull/3511 - remove this comments once merged
|
|
195
|
+
if (schema.extensions?.[appliedTransform] === true) {
|
|
196
|
+
return;
|
|
201
197
|
}
|
|
202
|
-
|
|
198
|
+
const patchedSchema = applyResponseCacheLogic(schema, idFields);
|
|
199
|
+
patchedSchema.extensions = {
|
|
200
|
+
...patchedSchema.extensions,
|
|
201
|
+
[appliedTransform]: true,
|
|
202
|
+
};
|
|
203
|
+
replaceSchema(patchedSchema);
|
|
203
204
|
},
|
|
204
205
|
onParse(parseCtx) {
|
|
205
206
|
return function onParseEnd(ctx) {
|
|
@@ -213,7 +214,6 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
213
214
|
};
|
|
214
215
|
},
|
|
215
216
|
async onExecute(ctx) {
|
|
216
|
-
var _a;
|
|
217
217
|
const identifier = new Map();
|
|
218
218
|
const types = new Set();
|
|
219
219
|
const context = {
|
|
@@ -253,107 +253,99 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
253
253
|
},
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
hit: true,
|
|
274
|
-
},
|
|
256
|
+
const documentString = getDocumentStringFromContext(ctx.args.contextValue);
|
|
257
|
+
if (documentString != null) {
|
|
258
|
+
const operationId = await buildResponseCacheKey({
|
|
259
|
+
documentString,
|
|
260
|
+
variableValues: ctx.args.variableValues,
|
|
261
|
+
operationName: ctx.args.operationName,
|
|
262
|
+
sessionId: session(ctx.args.contextValue),
|
|
263
|
+
});
|
|
264
|
+
if ((enabled?.(ctx.args.contextValue) ?? true) === true) {
|
|
265
|
+
const cachedResponse = await cache.get(operationId);
|
|
266
|
+
if (cachedResponse != null) {
|
|
267
|
+
if (includeExtensionMetadata) {
|
|
268
|
+
ctx.setResultAndStopExecution({
|
|
269
|
+
...cachedResponse,
|
|
270
|
+
extensions: {
|
|
271
|
+
responseCache: {
|
|
272
|
+
hit: true,
|
|
275
273
|
},
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
ctx.setResultAndStopExecution(cachedResponse);
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
274
|
+
},
|
|
275
|
+
});
|
|
282
276
|
}
|
|
277
|
+
else {
|
|
278
|
+
ctx.setResultAndStopExecution(cachedResponse);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
283
281
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
}));
|
|
298
|
-
}
|
|
299
|
-
return {
|
|
300
|
-
onExecuteDone({ result, setResult }) {
|
|
301
|
-
var _a;
|
|
302
|
-
if (core.isAsyncIterable(result)) {
|
|
303
|
-
// eslint-disable-next-line no-console
|
|
304
|
-
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
if (context.skip) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (!shouldCacheResult({ result })) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
// we only use the global ttl if no currentTtl has been determined.
|
|
314
|
-
const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
|
|
315
|
-
if (finalTtl === 0) {
|
|
316
|
-
if (includeExtensionMetadata) {
|
|
317
|
-
setResult({
|
|
318
|
-
...result,
|
|
319
|
-
extensions: {
|
|
320
|
-
responseCache: {
|
|
321
|
-
hit: false,
|
|
322
|
-
didCache: false,
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
});
|
|
282
|
+
}
|
|
283
|
+
if (ttlPerSchemaCoordinate) {
|
|
284
|
+
const typeInfo = new graphql.TypeInfo(ctx.args.schema);
|
|
285
|
+
graphql.visit(ctx.args.document, graphql.visitWithTypeInfo(typeInfo, {
|
|
286
|
+
Field(fieldNode) {
|
|
287
|
+
const parentType = typeInfo.getParentType();
|
|
288
|
+
if (parentType) {
|
|
289
|
+
const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
|
|
290
|
+
const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
|
|
291
|
+
if (maybeTtl !== undefined) {
|
|
292
|
+
context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
|
|
326
293
|
}
|
|
327
|
-
return;
|
|
328
294
|
}
|
|
329
|
-
|
|
295
|
+
},
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
onExecuteDone({ result, setResult }) {
|
|
300
|
+
if (core.isAsyncIterable(result)) {
|
|
301
|
+
// eslint-disable-next-line no-console
|
|
302
|
+
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!shouldCacheResult({ result })) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// we only use the global ttl if no currentTtl has been determined.
|
|
309
|
+
const finalTtl = context.currentTtl ?? globalTtl;
|
|
310
|
+
if (finalTtl === 0) {
|
|
330
311
|
if (includeExtensionMetadata) {
|
|
331
312
|
setResult({
|
|
332
313
|
...result,
|
|
333
314
|
extensions: {
|
|
334
315
|
responseCache: {
|
|
335
316
|
hit: false,
|
|
336
|
-
didCache:
|
|
337
|
-
ttl: finalTtl,
|
|
317
|
+
didCache: false,
|
|
338
318
|
},
|
|
339
319
|
},
|
|
340
320
|
});
|
|
341
321
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
cache.set(operationId, result, identifier.values(), finalTtl);
|
|
325
|
+
if (includeExtensionMetadata) {
|
|
326
|
+
setResult({
|
|
327
|
+
...result,
|
|
328
|
+
extensions: {
|
|
329
|
+
responseCache: {
|
|
330
|
+
hit: false,
|
|
331
|
+
didCache: true,
|
|
332
|
+
ttl: finalTtl,
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
};
|
|
351
339
|
}
|
|
340
|
+
// eslint-disable-next-line no-console
|
|
341
|
+
console.warn(`[useResponseCache] Failed extracting document string from the context. The response will not be cached or served from the cache. ` +
|
|
342
|
+
`If you are overriding the 'parse' behavior make sure to pass a custom 'getDocumentStringFromContext' function for getting the document string, which is required for building the response cache key.`);
|
|
343
|
+
return undefined;
|
|
352
344
|
},
|
|
353
345
|
};
|
|
354
346
|
}
|
|
355
347
|
function isOperationDefinition(node) {
|
|
356
|
-
return
|
|
348
|
+
return node?.kind === 'OperationDefinition';
|
|
357
349
|
}
|
|
358
350
|
function calculateTtl(typeTtl, currentTtl) {
|
|
359
351
|
if (typeof currentTtl === 'number') {
|
|
@@ -379,8 +371,7 @@ function applyResponseCacheLogic(schema, idFieldNames) {
|
|
|
379
371
|
return {
|
|
380
372
|
...fieldConfig,
|
|
381
373
|
resolve(src, args, context, info) {
|
|
382
|
-
|
|
383
|
-
const result = ((_a = fieldConfig.resolve) !== null && _a !== void 0 ? _a : graphql.defaultFieldResolver)(src, args, context, info);
|
|
374
|
+
const result = (fieldConfig.resolve ?? graphql.defaultFieldResolver)(src, args, context, info);
|
|
384
375
|
runWith(result, (id) => {
|
|
385
376
|
const ctx = context[contextSymbol];
|
|
386
377
|
if (ctx !== undefined) {
|
package/index.mjs
CHANGED
|
@@ -5,10 +5,9 @@ import { defaultFieldResolver, TypeInfo, visit, visitWithTypeInfo } from 'graphq
|
|
|
5
5
|
import jsonStableStringify from 'fast-json-stable-stringify';
|
|
6
6
|
|
|
7
7
|
const createInMemoryCache = (params) => {
|
|
8
|
-
|
|
9
|
-
const buildEntityId = (_a = params === null || params === void 0 ? void 0 : params.buildEntityId) !== null && _a !== void 0 ? _a : defaultBuildEntityId;
|
|
8
|
+
const buildEntityId = params?.buildEntityId ?? defaultBuildEntityId;
|
|
10
9
|
const cachedResponses = new LRU({
|
|
11
|
-
max: params
|
|
10
|
+
max: params?.max,
|
|
12
11
|
stale: false,
|
|
13
12
|
noDisposeOnSet: true,
|
|
14
13
|
dispose(responseId) {
|
|
@@ -18,13 +17,12 @@ const createInMemoryCache = (params) => {
|
|
|
18
17
|
const entityToResponseIds = new Map();
|
|
19
18
|
const responseIdToEntityIds = new Map();
|
|
20
19
|
function purgeResponse(responseId, shouldRemove = true) {
|
|
21
|
-
var _a;
|
|
22
20
|
const entityIds = responseIdToEntityIds.get(responseId);
|
|
23
21
|
// get entities related to the response
|
|
24
22
|
if (entityIds !== undefined) {
|
|
25
23
|
for (const entityId of entityIds) {
|
|
26
24
|
// remove the response mapping from the entity
|
|
27
|
-
|
|
25
|
+
entityToResponseIds.get(entityId)?.delete(responseId);
|
|
28
26
|
}
|
|
29
27
|
// remove all the entity mappings from the response
|
|
30
28
|
responseIdToEntityIds.delete(responseId);
|
|
@@ -72,8 +70,7 @@ const createInMemoryCache = (params) => {
|
|
|
72
70
|
}
|
|
73
71
|
},
|
|
74
72
|
get(responseId) {
|
|
75
|
-
|
|
76
|
-
return (_a = cachedResponses.get(responseId)) !== null && _a !== void 0 ? _a : null;
|
|
73
|
+
return cachedResponses.get(responseId) ?? null;
|
|
77
74
|
},
|
|
78
75
|
invalidate(entitiesToRemove) {
|
|
79
76
|
for (const { typename, id } of entitiesToRemove) {
|
|
@@ -118,7 +115,7 @@ const hashSHA256 = async (text) => {
|
|
|
118
115
|
if (nodeCrypto) {
|
|
119
116
|
return nodeCrypto.then(crypto => crypto.createHash('sha256').update(text).digest('hex'));
|
|
120
117
|
}
|
|
121
|
-
|
|
118
|
+
if (webCrypto) {
|
|
122
119
|
let buf;
|
|
123
120
|
if (typeof TextEncoder !== 'undefined') {
|
|
124
121
|
buf = new TextEncoder().encode(text);
|
|
@@ -153,15 +150,12 @@ const rawDocumentStringSymbol = Symbol('rawDocumentString');
|
|
|
153
150
|
* Default function used for building the response cache key.
|
|
154
151
|
* It is exported here for advanced use-cases. E.g. if you want to short circuit and serve responses from the cache on a global level in order to completely by-pass the GraphQL flow.
|
|
155
152
|
*/
|
|
156
|
-
const defaultBuildResponseCacheKey = params =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
(_c = params.sessionId) !== null && _c !== void 0 ? _c : '',
|
|
163
|
-
].join('|'));
|
|
164
|
-
};
|
|
153
|
+
const defaultBuildResponseCacheKey = params => hashSHA256([
|
|
154
|
+
params.documentString,
|
|
155
|
+
params.operationName ?? '',
|
|
156
|
+
jsonStableStringify(params.variableValues ?? {}),
|
|
157
|
+
params.sessionId ?? '',
|
|
158
|
+
].join('|'));
|
|
165
159
|
/**
|
|
166
160
|
* Default function used to check if the result should be cached.
|
|
167
161
|
*
|
|
@@ -182,18 +176,22 @@ const defaultGetDocumentStringFromContext = context => context[rawDocumentString
|
|
|
182
176
|
function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentStringFromContext = defaultGetDocumentStringFromContext, shouldCacheResult = defaultShouldCacheResult,
|
|
183
177
|
// eslint-disable-next-line dot-notation
|
|
184
178
|
includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
|
|
179
|
+
const appliedTransform = Symbol('responseCache.appliedTransform');
|
|
185
180
|
const ignoredTypesMap = new Set(ignoredTypes);
|
|
186
|
-
const schemaCache = new WeakMap();
|
|
187
181
|
// never cache Introspections
|
|
188
182
|
ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
|
|
189
183
|
return {
|
|
190
|
-
onSchemaChange(
|
|
191
|
-
|
|
192
|
-
if (schema
|
|
193
|
-
|
|
194
|
-
schemaCache.set(ctx.schema, schema);
|
|
184
|
+
onSchemaChange({ schema, replaceSchema }) {
|
|
185
|
+
// @ts-expect-error See https://github.com/graphql/graphql-js/pull/3511 - remove this comments once merged
|
|
186
|
+
if (schema.extensions?.[appliedTransform] === true) {
|
|
187
|
+
return;
|
|
195
188
|
}
|
|
196
|
-
|
|
189
|
+
const patchedSchema = applyResponseCacheLogic(schema, idFields);
|
|
190
|
+
patchedSchema.extensions = {
|
|
191
|
+
...patchedSchema.extensions,
|
|
192
|
+
[appliedTransform]: true,
|
|
193
|
+
};
|
|
194
|
+
replaceSchema(patchedSchema);
|
|
197
195
|
},
|
|
198
196
|
onParse(parseCtx) {
|
|
199
197
|
return function onParseEnd(ctx) {
|
|
@@ -207,7 +205,6 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
207
205
|
};
|
|
208
206
|
},
|
|
209
207
|
async onExecute(ctx) {
|
|
210
|
-
var _a;
|
|
211
208
|
const identifier = new Map();
|
|
212
209
|
const types = new Set();
|
|
213
210
|
const context = {
|
|
@@ -247,107 +244,99 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
247
244
|
},
|
|
248
245
|
};
|
|
249
246
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
hit: true,
|
|
268
|
-
},
|
|
247
|
+
const documentString = getDocumentStringFromContext(ctx.args.contextValue);
|
|
248
|
+
if (documentString != null) {
|
|
249
|
+
const operationId = await buildResponseCacheKey({
|
|
250
|
+
documentString,
|
|
251
|
+
variableValues: ctx.args.variableValues,
|
|
252
|
+
operationName: ctx.args.operationName,
|
|
253
|
+
sessionId: session(ctx.args.contextValue),
|
|
254
|
+
});
|
|
255
|
+
if ((enabled?.(ctx.args.contextValue) ?? true) === true) {
|
|
256
|
+
const cachedResponse = await cache.get(operationId);
|
|
257
|
+
if (cachedResponse != null) {
|
|
258
|
+
if (includeExtensionMetadata) {
|
|
259
|
+
ctx.setResultAndStopExecution({
|
|
260
|
+
...cachedResponse,
|
|
261
|
+
extensions: {
|
|
262
|
+
responseCache: {
|
|
263
|
+
hit: true,
|
|
269
264
|
},
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
ctx.setResultAndStopExecution(cachedResponse);
|
|
274
|
-
}
|
|
275
|
-
return;
|
|
265
|
+
},
|
|
266
|
+
});
|
|
276
267
|
}
|
|
268
|
+
else {
|
|
269
|
+
ctx.setResultAndStopExecution(cachedResponse);
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
277
272
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
}));
|
|
292
|
-
}
|
|
293
|
-
return {
|
|
294
|
-
onExecuteDone({ result, setResult }) {
|
|
295
|
-
var _a;
|
|
296
|
-
if (isAsyncIterable(result)) {
|
|
297
|
-
// eslint-disable-next-line no-console
|
|
298
|
-
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (context.skip) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
if (!shouldCacheResult({ result })) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
// we only use the global ttl if no currentTtl has been determined.
|
|
308
|
-
const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
|
|
309
|
-
if (finalTtl === 0) {
|
|
310
|
-
if (includeExtensionMetadata) {
|
|
311
|
-
setResult({
|
|
312
|
-
...result,
|
|
313
|
-
extensions: {
|
|
314
|
-
responseCache: {
|
|
315
|
-
hit: false,
|
|
316
|
-
didCache: false,
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
});
|
|
273
|
+
}
|
|
274
|
+
if (ttlPerSchemaCoordinate) {
|
|
275
|
+
const typeInfo = new TypeInfo(ctx.args.schema);
|
|
276
|
+
visit(ctx.args.document, visitWithTypeInfo(typeInfo, {
|
|
277
|
+
Field(fieldNode) {
|
|
278
|
+
const parentType = typeInfo.getParentType();
|
|
279
|
+
if (parentType) {
|
|
280
|
+
const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
|
|
281
|
+
const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
|
|
282
|
+
if (maybeTtl !== undefined) {
|
|
283
|
+
context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
|
|
320
284
|
}
|
|
321
|
-
return;
|
|
322
285
|
}
|
|
323
|
-
|
|
286
|
+
},
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
onExecuteDone({ result, setResult }) {
|
|
291
|
+
if (isAsyncIterable(result)) {
|
|
292
|
+
// eslint-disable-next-line no-console
|
|
293
|
+
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!shouldCacheResult({ result })) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// we only use the global ttl if no currentTtl has been determined.
|
|
300
|
+
const finalTtl = context.currentTtl ?? globalTtl;
|
|
301
|
+
if (finalTtl === 0) {
|
|
324
302
|
if (includeExtensionMetadata) {
|
|
325
303
|
setResult({
|
|
326
304
|
...result,
|
|
327
305
|
extensions: {
|
|
328
306
|
responseCache: {
|
|
329
307
|
hit: false,
|
|
330
|
-
didCache:
|
|
331
|
-
ttl: finalTtl,
|
|
308
|
+
didCache: false,
|
|
332
309
|
},
|
|
333
310
|
},
|
|
334
311
|
});
|
|
335
312
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
cache.set(operationId, result, identifier.values(), finalTtl);
|
|
316
|
+
if (includeExtensionMetadata) {
|
|
317
|
+
setResult({
|
|
318
|
+
...result,
|
|
319
|
+
extensions: {
|
|
320
|
+
responseCache: {
|
|
321
|
+
hit: false,
|
|
322
|
+
didCache: true,
|
|
323
|
+
ttl: finalTtl,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
};
|
|
345
330
|
}
|
|
331
|
+
// eslint-disable-next-line no-console
|
|
332
|
+
console.warn(`[useResponseCache] Failed extracting document string from the context. The response will not be cached or served from the cache. ` +
|
|
333
|
+
`If you are overriding the 'parse' behavior make sure to pass a custom 'getDocumentStringFromContext' function for getting the document string, which is required for building the response cache key.`);
|
|
334
|
+
return undefined;
|
|
346
335
|
},
|
|
347
336
|
};
|
|
348
337
|
}
|
|
349
338
|
function isOperationDefinition(node) {
|
|
350
|
-
return
|
|
339
|
+
return node?.kind === 'OperationDefinition';
|
|
351
340
|
}
|
|
352
341
|
function calculateTtl(typeTtl, currentTtl) {
|
|
353
342
|
if (typeof currentTtl === 'number') {
|
|
@@ -373,8 +362,7 @@ function applyResponseCacheLogic(schema, idFieldNames) {
|
|
|
373
362
|
return {
|
|
374
363
|
...fieldConfig,
|
|
375
364
|
resolve(src, args, context, info) {
|
|
376
|
-
|
|
377
|
-
const result = ((_a = fieldConfig.resolve) !== null && _a !== void 0 ? _a : defaultFieldResolver)(src, args, context, info);
|
|
365
|
+
const result = (fieldConfig.resolve ?? defaultFieldResolver)(src, args, context, info);
|
|
378
366
|
runWith(result, (id) => {
|
|
379
367
|
const ctx = context[contextSymbol];
|
|
380
368
|
if (ctx !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envelop/response-cache",
|
|
3
|
-
"version": "3.0.0-alpha-
|
|
3
|
+
"version": "3.0.0-alpha-d0d0776.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
|
-
"@envelop/core": "
|
|
6
|
+
"@envelop/core": "3.0.0-alpha-d0d0776.0",
|
|
7
7
|
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/n1ru4l/envelop.git",
|
|
17
17
|
"directory": "packages/plugins/response-cache"
|
|
18
18
|
},
|
|
19
19
|
"author": "Kamil Kisiela <kamilkisiela@gmail.com>",
|
|
@@ -26,12 +26,24 @@
|
|
|
26
26
|
},
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
-
"require":
|
|
30
|
-
|
|
29
|
+
"require": {
|
|
30
|
+
"default": "./index.js",
|
|
31
|
+
"types": "./index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"import": {
|
|
34
|
+
"default": "./index.mjs",
|
|
35
|
+
"types": "./index.d.ts"
|
|
36
|
+
}
|
|
31
37
|
},
|
|
32
38
|
"./*": {
|
|
33
|
-
"require":
|
|
34
|
-
|
|
39
|
+
"require": {
|
|
40
|
+
"default": "./*.js",
|
|
41
|
+
"types": "./*.d.ts"
|
|
42
|
+
},
|
|
43
|
+
"import": {
|
|
44
|
+
"default": "./*.mjs",
|
|
45
|
+
"types": "./*.d.ts"
|
|
46
|
+
}
|
|
35
47
|
},
|
|
36
48
|
"./package.json": "./package.json"
|
|
37
49
|
}
|