@envelop/response-cache 2.3.3-alpha-60f5d29.0 → 2.3.4-alpha-f55cd76.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/index.js CHANGED
@@ -154,7 +154,6 @@ const hashSHA256 = async (text) => {
154
154
  };
155
155
 
156
156
  const contextSymbol = Symbol('responseCache');
157
- const rawDocumentStringSymbol = Symbol('rawDocumentString');
158
157
  /**
159
158
  * Default function used for building the response cache key.
160
159
  * 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.
@@ -184,7 +183,15 @@ const defaultShouldCacheResult = (params) => {
184
183
  }
185
184
  return true;
186
185
  };
187
- const defaultGetDocumentStringFromContext = context => context[rawDocumentStringSymbol];
186
+ const documentStringByDocument = new WeakMap();
187
+ function defaultGetDocumentStringFromContext(_context, args) {
188
+ let documentString = documentStringByDocument.get(args.document);
189
+ if (!documentString) {
190
+ documentString = graphql.print(args.document);
191
+ documentStringByDocument.set(args.document, documentString);
192
+ }
193
+ return documentString;
194
+ }
188
195
  function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentStringFromContext = defaultGetDocumentStringFromContext, shouldCacheResult = defaultShouldCacheResult,
189
196
  // eslint-disable-next-line dot-notation
190
197
  includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
@@ -206,19 +213,15 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
206
213
  };
207
214
  replaceSchema(patchedSchema);
208
215
  },
209
- onParse(parseCtx) {
210
- return function onParseEnd(ctx) {
211
- if (ctx.result && 'kind' in ctx.result) {
212
- const source = parseCtx.params.source;
213
- const rawDocumentString = typeof source === 'string' ? source : source.body;
214
- ctx.extendContext({
215
- [rawDocumentStringSymbol]: rawDocumentString,
216
- });
216
+ onParse({ params: { source } }) {
217
+ return function onParseEnd({ result }) {
218
+ if (result != null && !(result instanceof Error)) {
219
+ documentStringByDocument.set(result, source.toString());
217
220
  }
218
221
  };
219
222
  },
220
223
  async onExecute(ctx) {
221
- var _a;
224
+ var _a, _b;
222
225
  const identifier = new Map();
223
226
  const types = new Set();
224
227
  const context = {
@@ -258,98 +261,93 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
258
261
  },
259
262
  };
260
263
  }
261
- const documentString = getDocumentStringFromContext(ctx.args.contextValue);
262
- if (documentString != null) {
263
- const operationId = await buildResponseCacheKey({
264
- documentString,
265
- variableValues: ctx.args.variableValues,
266
- operationName: ctx.args.operationName,
267
- sessionId: session(ctx.args.contextValue),
268
- });
269
- if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
270
- const cachedResponse = await cache.get(operationId);
271
- if (cachedResponse != null) {
272
- if (includeExtensionMetadata) {
273
- ctx.setResultAndStopExecution({
274
- ...cachedResponse,
275
- extensions: {
276
- responseCache: {
277
- hit: true,
278
- },
264
+ // Return a document string even if the user provided function doesn't return a valid result.
265
+ const documentString = (_a = getDocumentStringFromContext(ctx.args.contextValue, ctx.args)) !== null && _a !== void 0 ? _a : defaultGetDocumentStringFromContext(ctx.args.contextValue, ctx.args);
266
+ const operationId = await buildResponseCacheKey({
267
+ documentString,
268
+ variableValues: ctx.args.variableValues,
269
+ operationName: ctx.args.operationName,
270
+ sessionId: session(ctx.args.contextValue),
271
+ });
272
+ if (((_b = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _b !== void 0 ? _b : true) === true) {
273
+ const cachedResponse = await cache.get(operationId);
274
+ if (cachedResponse != null) {
275
+ if (includeExtensionMetadata) {
276
+ ctx.setResultAndStopExecution({
277
+ ...cachedResponse,
278
+ extensions: {
279
+ responseCache: {
280
+ hit: true,
279
281
  },
280
- });
281
- }
282
- else {
283
- ctx.setResultAndStopExecution(cachedResponse);
284
- }
285
- return;
282
+ },
283
+ });
286
284
  }
285
+ else {
286
+ ctx.setResultAndStopExecution(cachedResponse);
287
+ }
288
+ return;
287
289
  }
288
- if (ttlPerSchemaCoordinate) {
289
- const typeInfo = new graphql.TypeInfo(ctx.args.schema);
290
- graphql.visit(ctx.args.document, graphql.visitWithTypeInfo(typeInfo, {
291
- Field(fieldNode) {
292
- const parentType = typeInfo.getParentType();
293
- if (parentType) {
294
- const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
295
- const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
296
- if (maybeTtl !== undefined) {
297
- context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
298
- }
299
- }
300
- },
301
- }));
302
- }
303
- return {
304
- onExecuteDone({ result, setResult }) {
305
- var _a;
306
- if (core.isAsyncIterable(result)) {
307
- // eslint-disable-next-line no-console
308
- console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
309
- return;
310
- }
311
- if (context.skip) {
312
- return;
313
- }
314
- if (!shouldCacheResult({ result })) {
315
- return;
316
- }
317
- // we only use the global ttl if no currentTtl has been determined.
318
- const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
319
- if (finalTtl === 0) {
320
- if (includeExtensionMetadata) {
321
- setResult({
322
- ...result,
323
- extensions: {
324
- responseCache: {
325
- hit: false,
326
- didCache: false,
327
- },
328
- },
329
- });
290
+ }
291
+ if (ttlPerSchemaCoordinate) {
292
+ const typeInfo = new graphql.TypeInfo(ctx.args.schema);
293
+ graphql.visit(ctx.args.document, graphql.visitWithTypeInfo(typeInfo, {
294
+ Field(fieldNode) {
295
+ const parentType = typeInfo.getParentType();
296
+ if (parentType) {
297
+ const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
298
+ const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
299
+ if (maybeTtl !== undefined) {
300
+ context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
330
301
  }
331
- return;
332
302
  }
333
- cache.set(operationId, result, identifier.values(), finalTtl);
303
+ },
304
+ }));
305
+ }
306
+ return {
307
+ onExecuteDone({ result, setResult }) {
308
+ var _a;
309
+ if (core.isAsyncIterable(result)) {
310
+ // eslint-disable-next-line no-console
311
+ console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
312
+ return;
313
+ }
314
+ if (context.skip) {
315
+ return;
316
+ }
317
+ if (!shouldCacheResult({ result })) {
318
+ return;
319
+ }
320
+ // we only use the global ttl if no currentTtl has been determined.
321
+ const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
322
+ if (finalTtl === 0) {
334
323
  if (includeExtensionMetadata) {
335
324
  setResult({
336
325
  ...result,
337
326
  extensions: {
338
327
  responseCache: {
339
328
  hit: false,
340
- didCache: true,
341
- ttl: finalTtl,
329
+ didCache: false,
342
330
  },
343
331
  },
344
332
  });
345
333
  }
346
- },
347
- };
348
- }
349
- // eslint-disable-next-line no-console
350
- console.warn(`[useResponseCache] Failed extracting document string from the context. The response will not be cached or served from the cache. ` +
351
- `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.`);
352
- return undefined;
334
+ return;
335
+ }
336
+ cache.set(operationId, result, identifier.values(), finalTtl);
337
+ if (includeExtensionMetadata) {
338
+ setResult({
339
+ ...result,
340
+ extensions: {
341
+ responseCache: {
342
+ hit: false,
343
+ didCache: true,
344
+ ttl: finalTtl,
345
+ },
346
+ },
347
+ });
348
+ }
349
+ },
350
+ };
353
351
  },
354
352
  };
355
353
  }
package/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import LRU from 'lru-cache';
2
2
  import { isAsyncIterable } from '@envelop/core';
3
3
  import { mapSchema, MapperKind } from '@graphql-tools/utils';
4
- import { defaultFieldResolver, TypeInfo, visit, visitWithTypeInfo } from 'graphql';
4
+ import { print, defaultFieldResolver, TypeInfo, visit, visitWithTypeInfo } from 'graphql';
5
5
  import jsonStableStringify from 'fast-json-stable-stringify';
6
6
 
7
7
  const createInMemoryCache = (params) => {
@@ -148,7 +148,6 @@ const hashSHA256 = async (text) => {
148
148
  };
149
149
 
150
150
  const contextSymbol = Symbol('responseCache');
151
- const rawDocumentStringSymbol = Symbol('rawDocumentString');
152
151
  /**
153
152
  * Default function used for building the response cache key.
154
153
  * 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.
@@ -178,7 +177,15 @@ const defaultShouldCacheResult = (params) => {
178
177
  }
179
178
  return true;
180
179
  };
181
- const defaultGetDocumentStringFromContext = context => context[rawDocumentStringSymbol];
180
+ const documentStringByDocument = new WeakMap();
181
+ function defaultGetDocumentStringFromContext(_context, args) {
182
+ let documentString = documentStringByDocument.get(args.document);
183
+ if (!documentString) {
184
+ documentString = print(args.document);
185
+ documentStringByDocument.set(args.document, documentString);
186
+ }
187
+ return documentString;
188
+ }
182
189
  function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentStringFromContext = defaultGetDocumentStringFromContext, shouldCacheResult = defaultShouldCacheResult,
183
190
  // eslint-disable-next-line dot-notation
184
191
  includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
@@ -200,19 +207,15 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
200
207
  };
201
208
  replaceSchema(patchedSchema);
202
209
  },
203
- onParse(parseCtx) {
204
- return function onParseEnd(ctx) {
205
- if (ctx.result && 'kind' in ctx.result) {
206
- const source = parseCtx.params.source;
207
- const rawDocumentString = typeof source === 'string' ? source : source.body;
208
- ctx.extendContext({
209
- [rawDocumentStringSymbol]: rawDocumentString,
210
- });
210
+ onParse({ params: { source } }) {
211
+ return function onParseEnd({ result }) {
212
+ if (result != null && !(result instanceof Error)) {
213
+ documentStringByDocument.set(result, source.toString());
211
214
  }
212
215
  };
213
216
  },
214
217
  async onExecute(ctx) {
215
- var _a;
218
+ var _a, _b;
216
219
  const identifier = new Map();
217
220
  const types = new Set();
218
221
  const context = {
@@ -252,98 +255,93 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
252
255
  },
253
256
  };
254
257
  }
255
- const documentString = getDocumentStringFromContext(ctx.args.contextValue);
256
- if (documentString != null) {
257
- const operationId = await buildResponseCacheKey({
258
- documentString,
259
- variableValues: ctx.args.variableValues,
260
- operationName: ctx.args.operationName,
261
- sessionId: session(ctx.args.contextValue),
262
- });
263
- if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
264
- const cachedResponse = await cache.get(operationId);
265
- if (cachedResponse != null) {
266
- if (includeExtensionMetadata) {
267
- ctx.setResultAndStopExecution({
268
- ...cachedResponse,
269
- extensions: {
270
- responseCache: {
271
- hit: true,
272
- },
258
+ // Return a document string even if the user provided function doesn't return a valid result.
259
+ const documentString = (_a = getDocumentStringFromContext(ctx.args.contextValue, ctx.args)) !== null && _a !== void 0 ? _a : defaultGetDocumentStringFromContext(ctx.args.contextValue, ctx.args);
260
+ const operationId = await buildResponseCacheKey({
261
+ documentString,
262
+ variableValues: ctx.args.variableValues,
263
+ operationName: ctx.args.operationName,
264
+ sessionId: session(ctx.args.contextValue),
265
+ });
266
+ if (((_b = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _b !== void 0 ? _b : true) === true) {
267
+ const cachedResponse = await cache.get(operationId);
268
+ if (cachedResponse != null) {
269
+ if (includeExtensionMetadata) {
270
+ ctx.setResultAndStopExecution({
271
+ ...cachedResponse,
272
+ extensions: {
273
+ responseCache: {
274
+ hit: true,
273
275
  },
274
- });
275
- }
276
- else {
277
- ctx.setResultAndStopExecution(cachedResponse);
278
- }
279
- return;
276
+ },
277
+ });
280
278
  }
279
+ else {
280
+ ctx.setResultAndStopExecution(cachedResponse);
281
+ }
282
+ return;
281
283
  }
282
- if (ttlPerSchemaCoordinate) {
283
- const typeInfo = new TypeInfo(ctx.args.schema);
284
- visit(ctx.args.document, visitWithTypeInfo(typeInfo, {
285
- Field(fieldNode) {
286
- const parentType = typeInfo.getParentType();
287
- if (parentType) {
288
- const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
289
- const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
290
- if (maybeTtl !== undefined) {
291
- context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
292
- }
293
- }
294
- },
295
- }));
296
- }
297
- return {
298
- onExecuteDone({ result, setResult }) {
299
- var _a;
300
- if (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 (context.skip) {
306
- return;
307
- }
308
- if (!shouldCacheResult({ result })) {
309
- return;
310
- }
311
- // we only use the global ttl if no currentTtl has been determined.
312
- const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
313
- if (finalTtl === 0) {
314
- if (includeExtensionMetadata) {
315
- setResult({
316
- ...result,
317
- extensions: {
318
- responseCache: {
319
- hit: false,
320
- didCache: false,
321
- },
322
- },
323
- });
284
+ }
285
+ if (ttlPerSchemaCoordinate) {
286
+ const typeInfo = new TypeInfo(ctx.args.schema);
287
+ visit(ctx.args.document, visitWithTypeInfo(typeInfo, {
288
+ Field(fieldNode) {
289
+ const parentType = typeInfo.getParentType();
290
+ if (parentType) {
291
+ const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
292
+ const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
293
+ if (maybeTtl !== undefined) {
294
+ context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
324
295
  }
325
- return;
326
296
  }
327
- cache.set(operationId, result, identifier.values(), finalTtl);
297
+ },
298
+ }));
299
+ }
300
+ return {
301
+ onExecuteDone({ result, setResult }) {
302
+ var _a;
303
+ if (isAsyncIterable(result)) {
304
+ // eslint-disable-next-line no-console
305
+ console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
306
+ return;
307
+ }
308
+ if (context.skip) {
309
+ return;
310
+ }
311
+ if (!shouldCacheResult({ result })) {
312
+ return;
313
+ }
314
+ // we only use the global ttl if no currentTtl has been determined.
315
+ const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
316
+ if (finalTtl === 0) {
328
317
  if (includeExtensionMetadata) {
329
318
  setResult({
330
319
  ...result,
331
320
  extensions: {
332
321
  responseCache: {
333
322
  hit: false,
334
- didCache: true,
335
- ttl: finalTtl,
323
+ didCache: false,
336
324
  },
337
325
  },
338
326
  });
339
327
  }
340
- },
341
- };
342
- }
343
- // eslint-disable-next-line no-console
344
- console.warn(`[useResponseCache] Failed extracting document string from the context. The response will not be cached or served from the cache. ` +
345
- `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.`);
346
- return undefined;
328
+ return;
329
+ }
330
+ cache.set(operationId, result, identifier.values(), finalTtl);
331
+ if (includeExtensionMetadata) {
332
+ setResult({
333
+ ...result,
334
+ extensions: {
335
+ responseCache: {
336
+ hit: false,
337
+ didCache: true,
338
+ ttl: finalTtl,
339
+ },
340
+ },
341
+ });
342
+ }
343
+ },
344
+ };
347
345
  },
348
346
  };
349
347
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@envelop/response-cache",
3
- "version": "2.3.3-alpha-60f5d29.0",
3
+ "version": "2.3.4-alpha-f55cd76.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "@envelop/core": "2.3.3-alpha-60f5d29.0",
6
+ "@envelop/core": "^2.3.3",
7
7
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
8
8
  },
9
9
  "dependencies": {
package/plugin.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare type BuildResponseCacheKeyFunction = (params: {
14
14
  /** optional sessionId for make unique cache keys based on the session. */
15
15
  sessionId?: Maybe<string>;
16
16
  }) => Promise<string>;
17
- export declare type GetDocumentStringFromContextFunction = (params: DefaultContext) => Maybe<string>;
17
+ export declare type GetDocumentStringFromContextFunction = (context: DefaultContext, executionArgs: ExecutionArgs) => Maybe<string>;
18
18
  export declare type ShouldCacheResultFunction = (params: {
19
19
  result: ExecutionResult;
20
20
  }) => Boolean;
@@ -73,9 +73,10 @@ export declare type UseResponseCacheParameter<C = any> = {
73
73
  buildResponseCacheKey?: BuildResponseCacheKeyFunction;
74
74
  /**
75
75
  * Function used for reading the document string that is used for building the response cache key from the context object.
76
- * By default, the useResponseCache plugin hooks into onParse and writes the original operation string to the context.
76
+ * By default, the useResponseCache plugin hooks into onParse and caches the original operation string in a WeakMap.
77
77
  * If you are hard overriding parse you need to set this function, otherwise responses will not be cached or served from the cache.
78
78
  * Defaults to `defaultGetDocumentStringFromContext`
79
+ *
79
80
  */
80
81
  getDocumentStringFromContext?: GetDocumentStringFromContextFunction;
81
82
  /**
@@ -104,5 +105,5 @@ export declare const defaultBuildResponseCacheKey: BuildResponseCacheKeyFunction
104
105
  * By default, results with errors (unexpected, EnvelopError, or GraphQLError) are not cached.
105
106
  */
106
107
  export declare const defaultShouldCacheResult: ShouldCacheResultFunction;
107
- export declare const defaultGetDocumentStringFromContext: GetDocumentStringFromContextFunction;
108
+ export declare function defaultGetDocumentStringFromContext(_context: DefaultContext, args: ExecutionArgs): string;
108
109
  export declare function useResponseCache({ cache, ttl: globalTtl, session, enabled, ignoredTypes, ttlPerType, ttlPerSchemaCoordinate, idFields, invalidateViaMutation, buildResponseCacheKey, getDocumentStringFromContext, shouldCacheResult, includeExtensionMetadata, }?: UseResponseCacheParameter): Plugin;