@envelop/response-cache 3.0.0-alpha-6a4e0e2.0 → 3.0.0-alpha-e8a9318.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.
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultGetDocumentString = exports.useCacheDocumentString = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const documentStringByDocument = new WeakMap();
6
+ function useCacheDocumentString() {
7
+ return {
8
+ onParse({ params: { source } }) {
9
+ return function onParseEnd({ result }) {
10
+ if (result != null && !(result instanceof Error)) {
11
+ documentStringByDocument.set(result, source.toString());
12
+ }
13
+ };
14
+ },
15
+ };
16
+ }
17
+ exports.useCacheDocumentString = useCacheDocumentString;
18
+ function defaultGetDocumentString(executionArgs) {
19
+ let documentString = documentStringByDocument.get(executionArgs.document);
20
+ if (documentString == null) {
21
+ documentString = (0, graphql_1.print)(executionArgs.document);
22
+ documentStringByDocument.set(executionArgs.document, documentString);
23
+ }
24
+ return documentString;
25
+ }
26
+ exports.defaultGetDocumentString = defaultGetDocumentString;
package/cjs/cache.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hashSHA256 = void 0;
4
+ const webCrypto = globalThis.crypto;
5
+ const sha256Browser = (bytes) => {
6
+ const hash = webCrypto.subtle.digest({ name: 'SHA-256' }, bytes);
7
+ return new Promise((resolve, reject) => {
8
+ Promise.resolve(hash)
9
+ .then(function (result) {
10
+ resolve(new Uint8Array(result));
11
+ })
12
+ .catch(function (error) {
13
+ reject(error);
14
+ });
15
+ });
16
+ };
17
+ let nodeCrypto;
18
+ if (typeof window === 'undefined' && !webCrypto) {
19
+ // Indirect eval'd require/import to guarantee no side-effects in module scope
20
+ // (optimization for minifier)
21
+ try {
22
+ // eslint-disable-next-line no-new-func
23
+ nodeCrypto = Promise.resolve(new Function('require', 'return require("crypto")')(require));
24
+ }
25
+ catch (_error) {
26
+ try {
27
+ // eslint-disable-next-line no-new-func
28
+ nodeCrypto = new Function('return import("crypto")')();
29
+ }
30
+ catch (_error) { }
31
+ }
32
+ }
33
+ const hashSHA256 = async (text) => {
34
+ // Node.js support
35
+ if (nodeCrypto) {
36
+ return nodeCrypto.then(crypto => crypto.createHash('sha256').update(text).digest('hex'));
37
+ }
38
+ if (webCrypto) {
39
+ let buf;
40
+ if (typeof TextEncoder !== 'undefined') {
41
+ buf = new TextEncoder().encode(text);
42
+ }
43
+ else {
44
+ buf = new Uint8Array(text.length);
45
+ for (let i = 0, l = text.length; i < l; i++) {
46
+ // NOTE: We assume that the input GraphQL Query only uses UTF-8 at most
47
+ // since GraphQL mostly consists of ASCII, this is completely fine
48
+ buf[i] = text.charCodeAt(i);
49
+ }
50
+ }
51
+ const out = await sha256Browser(buf);
52
+ let hash = '';
53
+ for (let i = 0, l = out.length; i < l; i++) {
54
+ const hex = out[i].toString(16);
55
+ hash += '00'.slice(0, Math.max(0, 2 - hex.length)) + hex;
56
+ }
57
+ return hash;
58
+ }
59
+ if (process.env.NODE_ENV !== 'production') {
60
+ // eslint-disable-next-line no-console
61
+ console.warn('[@envelop/response-cache]: The Node Crypto and Web Crypto APIs are not available.\n' +
62
+ 'This is an unexpected error. Please report it by filing a GitHub Issue.');
63
+ }
64
+ return Promise.resolve('');
65
+ };
66
+ exports.hashSHA256 = hashSHA256;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultBuildEntityId = exports.createInMemoryCache = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const lru_cache_1 = tslib_1.__importDefault(require("lru-cache"));
6
+ const createInMemoryCache = (params) => {
7
+ var _a;
8
+ const buildEntityId = (_a = params === null || params === void 0 ? void 0 : params.buildEntityId) !== null && _a !== void 0 ? _a : exports.defaultBuildEntityId;
9
+ const cachedResponses = new lru_cache_1.default({
10
+ max: params === null || params === void 0 ? void 0 : params.max,
11
+ stale: false,
12
+ noDisposeOnSet: true,
13
+ dispose(responseId) {
14
+ purgeResponse(responseId, false);
15
+ },
16
+ });
17
+ const entityToResponseIds = new Map();
18
+ const responseIdToEntityIds = new Map();
19
+ function purgeResponse(responseId, shouldRemove = true) {
20
+ var _a;
21
+ const entityIds = responseIdToEntityIds.get(responseId);
22
+ // get entities related to the response
23
+ if (entityIds !== undefined) {
24
+ for (const entityId of entityIds) {
25
+ // remove the response mapping from the entity
26
+ (_a = entityToResponseIds.get(entityId)) === null || _a === void 0 ? void 0 : _a.delete(responseId);
27
+ }
28
+ // remove all the entity mappings from the response
29
+ responseIdToEntityIds.delete(responseId);
30
+ }
31
+ if (shouldRemove) {
32
+ // remove the response from the cache
33
+ cachedResponses.del(responseId);
34
+ }
35
+ }
36
+ function purgeEntity(entity) {
37
+ const responseIds = entityToResponseIds.get(entity);
38
+ if (responseIds !== undefined) {
39
+ for (const responseId of responseIds) {
40
+ purgeResponse(responseId);
41
+ }
42
+ }
43
+ }
44
+ return {
45
+ set(responseId, result, collectedEntities, ttl) {
46
+ cachedResponses.set(responseId, result, ttl);
47
+ const entityIds = new Set();
48
+ responseIdToEntityIds.set(responseId, entityIds);
49
+ for (const { typename, id } of collectedEntities) {
50
+ let operationIds = entityToResponseIds.get(typename);
51
+ if (operationIds == null) {
52
+ operationIds = new Set();
53
+ entityToResponseIds.set(typename, operationIds);
54
+ }
55
+ // typename => operation
56
+ operationIds.add(responseId);
57
+ // operation => typename
58
+ entityIds.add(typename);
59
+ if (id !== undefined) {
60
+ const entityId = buildEntityId(typename, id);
61
+ let responseIds = entityToResponseIds.get(entityId);
62
+ if (responseIds == null) {
63
+ responseIds = new Set();
64
+ entityToResponseIds.set(entityId, responseIds);
65
+ }
66
+ // typename:id => operation
67
+ responseIds.add(responseId);
68
+ // operation => typename:id
69
+ entityIds.add(entityId);
70
+ }
71
+ }
72
+ },
73
+ get(responseId) {
74
+ var _a;
75
+ return (_a = cachedResponses.get(responseId)) !== null && _a !== void 0 ? _a : null;
76
+ },
77
+ invalidate(entitiesToRemove) {
78
+ for (const { typename, id } of entitiesToRemove) {
79
+ purgeEntity(id != null ? buildEntityId(typename, id) : typename);
80
+ }
81
+ },
82
+ };
83
+ };
84
+ exports.createInMemoryCache = createInMemoryCache;
85
+ const defaultBuildEntityId = (typename, id) => `${typename}:${id}`;
86
+ exports.defaultBuildEntityId = defaultBuildEntityId;
package/cjs/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./in-memory-cache.js"), exports);
5
+ tslib_1.__exportStar(require("./plugin.js"), exports);
6
+ tslib_1.__exportStar(require("./cache.js"), exports);
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -1,158 +1,14 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
-
7
- const LRU = _interopDefault(require('lru-cache'));
8
- const core = require('@envelop/core');
9
- const utils = require('@graphql-tools/utils');
10
- const graphql = require('graphql');
11
- const jsonStableStringify = _interopDefault(require('fast-json-stable-stringify'));
12
-
13
- const createInMemoryCache = (params) => {
14
- var _a;
15
- const buildEntityId = (_a = params === null || params === void 0 ? void 0 : params.buildEntityId) !== null && _a !== void 0 ? _a : defaultBuildEntityId;
16
- const cachedResponses = new LRU({
17
- max: params === null || params === void 0 ? void 0 : params.max,
18
- stale: false,
19
- noDisposeOnSet: true,
20
- dispose(responseId) {
21
- purgeResponse(responseId, false);
22
- },
23
- });
24
- const entityToResponseIds = new Map();
25
- const responseIdToEntityIds = new Map();
26
- function purgeResponse(responseId, shouldRemove = true) {
27
- var _a;
28
- const entityIds = responseIdToEntityIds.get(responseId);
29
- // get entities related to the response
30
- if (entityIds !== undefined) {
31
- for (const entityId of entityIds) {
32
- // remove the response mapping from the entity
33
- (_a = entityToResponseIds.get(entityId)) === null || _a === void 0 ? void 0 : _a.delete(responseId);
34
- }
35
- // remove all the entity mappings from the response
36
- responseIdToEntityIds.delete(responseId);
37
- }
38
- if (shouldRemove) {
39
- // remove the response from the cache
40
- cachedResponses.del(responseId);
41
- }
42
- }
43
- function purgeEntity(entity) {
44
- const responseIds = entityToResponseIds.get(entity);
45
- if (responseIds !== undefined) {
46
- for (const responseId of responseIds) {
47
- purgeResponse(responseId);
48
- }
49
- }
50
- }
51
- return {
52
- set(responseId, result, collectedEntities, ttl) {
53
- cachedResponses.set(responseId, result, ttl);
54
- const entityIds = new Set();
55
- responseIdToEntityIds.set(responseId, entityIds);
56
- for (const { typename, id } of collectedEntities) {
57
- let operationIds = entityToResponseIds.get(typename);
58
- if (operationIds == null) {
59
- operationIds = new Set();
60
- entityToResponseIds.set(typename, operationIds);
61
- }
62
- // typename => operation
63
- operationIds.add(responseId);
64
- // operation => typename
65
- entityIds.add(typename);
66
- if (id !== undefined) {
67
- const entityId = buildEntityId(typename, id);
68
- let responseIds = entityToResponseIds.get(entityId);
69
- if (responseIds == null) {
70
- responseIds = new Set();
71
- entityToResponseIds.set(entityId, responseIds);
72
- }
73
- // typename:id => operation
74
- responseIds.add(responseId);
75
- // operation => typename:id
76
- entityIds.add(entityId);
77
- }
78
- }
79
- },
80
- get(responseId) {
81
- var _a;
82
- return (_a = cachedResponses.get(responseId)) !== null && _a !== void 0 ? _a : null;
83
- },
84
- invalidate(entitiesToRemove) {
85
- for (const { typename, id } of entitiesToRemove) {
86
- purgeEntity(id != null ? buildEntityId(typename, id) : typename);
87
- }
88
- },
89
- };
90
- };
91
- const defaultBuildEntityId = (typename, id) => `${typename}:${id}`;
92
-
93
- const webCrypto = globalThis.crypto;
94
- const sha256Browser = (bytes) => {
95
- const hash = webCrypto.subtle.digest({ name: 'SHA-256' }, bytes);
96
- return new Promise((resolve, reject) => {
97
- Promise.resolve(hash)
98
- .then(function (result) {
99
- resolve(new Uint8Array(result));
100
- })
101
- .catch(function (error) {
102
- reject(error);
103
- });
104
- });
105
- };
106
- let nodeCrypto;
107
- if (typeof window === 'undefined' && !webCrypto) {
108
- // Indirect eval'd require/import to guarantee no side-effects in module scope
109
- // (optimization for minifier)
110
- try {
111
- // eslint-disable-next-line no-new-func
112
- nodeCrypto = Promise.resolve(new Function('require', 'return require("crypto")')(require));
113
- }
114
- catch (_error) {
115
- try {
116
- // eslint-disable-next-line no-new-func
117
- nodeCrypto = new Function('return import("crypto")')();
118
- }
119
- catch (_error) { }
120
- }
121
- }
122
- const hashSHA256 = async (text) => {
123
- // Node.js support
124
- if (nodeCrypto) {
125
- return nodeCrypto.then(crypto => crypto.createHash('sha256').update(text).digest('hex'));
126
- }
127
- if (webCrypto) {
128
- let buf;
129
- if (typeof TextEncoder !== 'undefined') {
130
- buf = new TextEncoder().encode(text);
131
- }
132
- else {
133
- buf = new Uint8Array(text.length);
134
- for (let i = 0, l = text.length; i < l; i++) {
135
- // NOTE: We assume that the input GraphQL Query only uses UTF-8 at most
136
- // since GraphQL mostly consists of ASCII, this is completely fine
137
- buf[i] = text.charCodeAt(i);
138
- }
139
- }
140
- const out = await sha256Browser(buf);
141
- let hash = '';
142
- for (let i = 0, l = out.length; i < l; i++) {
143
- const hex = out[i].toString(16);
144
- hash += '00'.slice(0, Math.max(0, 2 - hex.length)) + hex;
145
- }
146
- return hash;
147
- }
148
- if (process.env.NODE_ENV !== 'production') {
149
- // eslint-disable-next-line no-console
150
- console.warn('[@envelop/response-cache]: The Node Crypto and Web Crypto APIs are not available.\n' +
151
- 'This is an unexpected error. Please report it by filing a GitHub Issue.');
152
- }
153
- return Promise.resolve('');
154
- };
155
-
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useResponseCache = exports.defaultShouldCacheResult = exports.defaultBuildResponseCacheKey = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const core_1 = require("@envelop/core");
6
+ const utils_1 = require("@graphql-tools/utils");
7
+ const graphql_1 = require("graphql");
8
+ const fast_json_stable_stringify_1 = tslib_1.__importDefault(require("fast-json-stable-stringify"));
9
+ const in_memory_cache_js_1 = require("./in-memory-cache.js");
10
+ const hashSHA256_js_1 = require("./hashSHA256.js");
11
+ const cache_document_str_js_1 = require("./cache-document-str.js");
156
12
  const contextSymbol = Symbol('responseCache');
157
13
  /**
158
14
  * Default function used for building the response cache key.
@@ -160,13 +16,14 @@ const contextSymbol = Symbol('responseCache');
160
16
  */
161
17
  const defaultBuildResponseCacheKey = params => {
162
18
  var _a, _b, _c;
163
- return hashSHA256([
19
+ return (0, hashSHA256_js_1.hashSHA256)([
164
20
  params.documentString,
165
21
  (_a = params.operationName) !== null && _a !== void 0 ? _a : '',
166
- jsonStableStringify((_b = params.variableValues) !== null && _b !== void 0 ? _b : {}),
22
+ (0, fast_json_stable_stringify_1.default)((_b = params.variableValues) !== null && _b !== void 0 ? _b : {}),
167
23
  (_c = params.sessionId) !== null && _c !== void 0 ? _c : '',
168
24
  ].join('|'));
169
25
  };
26
+ exports.defaultBuildResponseCacheKey = defaultBuildResponseCacheKey;
170
27
  /**
171
28
  * Default function used to check if the result should be cached.
172
29
  *
@@ -183,16 +40,8 @@ const defaultShouldCacheResult = (params) => {
183
40
  }
184
41
  return true;
185
42
  };
186
- const documentStringByDocument = new WeakMap();
187
- function defaultGetDocumentString(executionArgs) {
188
- let documentString = documentStringByDocument.get(executionArgs.document);
189
- if (!documentString) {
190
- documentString = graphql.print(executionArgs.document);
191
- documentStringByDocument.set(executionArgs.document, documentString);
192
- }
193
- return documentString;
194
- }
195
- function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = defaultShouldCacheResult,
43
+ exports.defaultShouldCacheResult = defaultShouldCacheResult;
44
+ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache)(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = exports.defaultBuildResponseCacheKey, getDocumentString = cache_document_str_js_1.defaultGetDocumentString, shouldCacheResult = exports.defaultShouldCacheResult,
196
45
  // eslint-disable-next-line dot-notation
197
46
  includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
198
47
  const appliedTransform = Symbol('responseCache.appliedTransform');
@@ -200,6 +49,11 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
200
49
  // never cache Introspections
201
50
  ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
202
51
  return {
52
+ onPluginInit({ addPlugin }) {
53
+ if (getDocumentString === cache_document_str_js_1.defaultGetDocumentString) {
54
+ addPlugin((0, cache_document_str_js_1.useCacheDocumentString)());
55
+ }
56
+ },
203
57
  onSchemaChange({ schema, replaceSchema }) {
204
58
  var _a;
205
59
  // @ts-expect-error See https://github.com/graphql/graphql-js/pull/3511 - remove this comments once merged
@@ -213,13 +67,6 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
213
67
  };
214
68
  replaceSchema(patchedSchema);
215
69
  },
216
- onParse({ params: { source } }) {
217
- return function onParseEnd({ result }) {
218
- if (result != null && !(result instanceof Error)) {
219
- documentStringByDocument.set(result, source.toString());
220
- }
221
- };
222
- },
223
70
  async onExecute(ctx) {
224
71
  var _a;
225
72
  const identifier = new Map();
@@ -241,7 +88,7 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
241
88
  }
242
89
  return {
243
90
  onExecuteDone({ result, setResult }) {
244
- if (core.isAsyncIterable(result)) {
91
+ if ((0, core_1.isAsyncIterable)(result)) {
245
92
  // eslint-disable-next-line no-console
246
93
  console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
247
94
  return;
@@ -263,8 +110,9 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
263
110
  }
264
111
  const operationId = await buildResponseCacheKey({
265
112
  documentString: getDocumentString(ctx.args),
113
+ variableValues: ctx.args.variableValues,
114
+ operationName: ctx.args.operationName,
266
115
  sessionId: session(ctx.args.contextValue),
267
- ...ctx.args,
268
116
  });
269
117
  if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
270
118
  const cachedResponse = await cache.get(operationId);
@@ -286,8 +134,8 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
286
134
  }
287
135
  }
288
136
  if (ttlPerSchemaCoordinate) {
289
- const typeInfo = new graphql.TypeInfo(ctx.args.schema);
290
- graphql.visit(ctx.args.document, graphql.visitWithTypeInfo(typeInfo, {
137
+ const typeInfo = new graphql_1.TypeInfo(ctx.args.schema);
138
+ (0, graphql_1.visit)(ctx.args.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
291
139
  Field(fieldNode) {
292
140
  const parentType = typeInfo.getParentType();
293
141
  if (parentType) {
@@ -303,7 +151,7 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
303
151
  return {
304
152
  onExecuteDone({ result, setResult }) {
305
153
  var _a;
306
- if (core.isAsyncIterable(result)) {
154
+ if ((0, core_1.isAsyncIterable)(result)) {
307
155
  // eslint-disable-next-line no-console
308
156
  console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
309
157
  return;
@@ -348,6 +196,7 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
348
196
  },
349
197
  };
350
198
  }
199
+ exports.useResponseCache = useResponseCache;
351
200
  function isOperationDefinition(node) {
352
201
  return (node === null || node === void 0 ? void 0 : node.kind) === 'OperationDefinition';
353
202
  }
@@ -369,14 +218,14 @@ function runWith(input, callback) {
369
218
  }
370
219
  }
371
220
  function applyResponseCacheLogic(schema, idFieldNames) {
372
- return utils.mapSchema(schema, {
373
- [utils.MapperKind.OBJECT_FIELD](fieldConfig, fieldName, typename) {
221
+ return (0, utils_1.mapSchema)(schema, {
222
+ [utils_1.MapperKind.OBJECT_FIELD](fieldConfig, fieldName, typename) {
374
223
  if (idFieldNames.includes(fieldName)) {
375
224
  return {
376
225
  ...fieldConfig,
377
226
  resolve(src, args, context, info) {
378
227
  var _a;
379
- const result = ((_a = fieldConfig.resolve) !== null && _a !== void 0 ? _a : graphql.defaultFieldResolver)(src, args, context, info);
228
+ const result = ((_a = fieldConfig.resolve) !== null && _a !== void 0 ? _a : graphql_1.defaultFieldResolver)(src, args, context, info);
380
229
  runWith(result, (id) => {
381
230
  const ctx = context[contextSymbol];
382
231
  if (ctx !== undefined) {
@@ -404,10 +253,3 @@ function applyResponseCacheLogic(schema, idFieldNames) {
404
253
  },
405
254
  });
406
255
  }
407
-
408
- exports.createInMemoryCache = createInMemoryCache;
409
- exports.defaultBuildEntityId = defaultBuildEntityId;
410
- exports.defaultBuildResponseCacheKey = defaultBuildResponseCacheKey;
411
- exports.defaultGetDocumentString = defaultGetDocumentString;
412
- exports.defaultShouldCacheResult = defaultShouldCacheResult;
413
- exports.useResponseCache = useResponseCache;
@@ -0,0 +1,21 @@
1
+ import { print } from 'graphql';
2
+ const documentStringByDocument = new WeakMap();
3
+ export function useCacheDocumentString() {
4
+ return {
5
+ onParse({ params: { source } }) {
6
+ return function onParseEnd({ result }) {
7
+ if (result != null && !(result instanceof Error)) {
8
+ documentStringByDocument.set(result, source.toString());
9
+ }
10
+ };
11
+ },
12
+ };
13
+ }
14
+ export function defaultGetDocumentString(executionArgs) {
15
+ let documentString = documentStringByDocument.get(executionArgs.document);
16
+ if (documentString == null) {
17
+ documentString = print(executionArgs.document);
18
+ documentStringByDocument.set(executionArgs.document, documentString);
19
+ }
20
+ return documentString;
21
+ }
package/esm/cache.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ const webCrypto = globalThis.crypto;
2
+ const sha256Browser = (bytes) => {
3
+ const hash = webCrypto.subtle.digest({ name: 'SHA-256' }, bytes);
4
+ return new Promise((resolve, reject) => {
5
+ Promise.resolve(hash)
6
+ .then(function (result) {
7
+ resolve(new Uint8Array(result));
8
+ })
9
+ .catch(function (error) {
10
+ reject(error);
11
+ });
12
+ });
13
+ };
14
+ let nodeCrypto;
15
+ if (typeof window === 'undefined' && !webCrypto) {
16
+ // Indirect eval'd require/import to guarantee no side-effects in module scope
17
+ // (optimization for minifier)
18
+ try {
19
+ // eslint-disable-next-line no-new-func
20
+ nodeCrypto = Promise.resolve(new Function('require', 'return require("crypto")')(require));
21
+ }
22
+ catch (_error) {
23
+ try {
24
+ // eslint-disable-next-line no-new-func
25
+ nodeCrypto = new Function('return import("crypto")')();
26
+ }
27
+ catch (_error) { }
28
+ }
29
+ }
30
+ export const hashSHA256 = async (text) => {
31
+ // Node.js support
32
+ if (nodeCrypto) {
33
+ return nodeCrypto.then(crypto => crypto.createHash('sha256').update(text).digest('hex'));
34
+ }
35
+ if (webCrypto) {
36
+ let buf;
37
+ if (typeof TextEncoder !== 'undefined') {
38
+ buf = new TextEncoder().encode(text);
39
+ }
40
+ else {
41
+ buf = new Uint8Array(text.length);
42
+ for (let i = 0, l = text.length; i < l; i++) {
43
+ // NOTE: We assume that the input GraphQL Query only uses UTF-8 at most
44
+ // since GraphQL mostly consists of ASCII, this is completely fine
45
+ buf[i] = text.charCodeAt(i);
46
+ }
47
+ }
48
+ const out = await sha256Browser(buf);
49
+ let hash = '';
50
+ for (let i = 0, l = out.length; i < l; i++) {
51
+ const hex = out[i].toString(16);
52
+ hash += '00'.slice(0, Math.max(0, 2 - hex.length)) + hex;
53
+ }
54
+ return hash;
55
+ }
56
+ if (process.env.NODE_ENV !== 'production') {
57
+ // eslint-disable-next-line no-console
58
+ console.warn('[@envelop/response-cache]: The Node Crypto and Web Crypto APIs are not available.\n' +
59
+ 'This is an unexpected error. Please report it by filing a GitHub Issue.');
60
+ }
61
+ return Promise.resolve('');
62
+ };
@@ -0,0 +1,80 @@
1
+ import LRU from 'lru-cache';
2
+ export const createInMemoryCache = (params) => {
3
+ var _a;
4
+ const buildEntityId = (_a = params === null || params === void 0 ? void 0 : params.buildEntityId) !== null && _a !== void 0 ? _a : defaultBuildEntityId;
5
+ const cachedResponses = new LRU({
6
+ max: params === null || params === void 0 ? void 0 : params.max,
7
+ stale: false,
8
+ noDisposeOnSet: true,
9
+ dispose(responseId) {
10
+ purgeResponse(responseId, false);
11
+ },
12
+ });
13
+ const entityToResponseIds = new Map();
14
+ const responseIdToEntityIds = new Map();
15
+ function purgeResponse(responseId, shouldRemove = true) {
16
+ var _a;
17
+ const entityIds = responseIdToEntityIds.get(responseId);
18
+ // get entities related to the response
19
+ if (entityIds !== undefined) {
20
+ for (const entityId of entityIds) {
21
+ // remove the response mapping from the entity
22
+ (_a = entityToResponseIds.get(entityId)) === null || _a === void 0 ? void 0 : _a.delete(responseId);
23
+ }
24
+ // remove all the entity mappings from the response
25
+ responseIdToEntityIds.delete(responseId);
26
+ }
27
+ if (shouldRemove) {
28
+ // remove the response from the cache
29
+ cachedResponses.del(responseId);
30
+ }
31
+ }
32
+ function purgeEntity(entity) {
33
+ const responseIds = entityToResponseIds.get(entity);
34
+ if (responseIds !== undefined) {
35
+ for (const responseId of responseIds) {
36
+ purgeResponse(responseId);
37
+ }
38
+ }
39
+ }
40
+ return {
41
+ set(responseId, result, collectedEntities, ttl) {
42
+ cachedResponses.set(responseId, result, ttl);
43
+ const entityIds = new Set();
44
+ responseIdToEntityIds.set(responseId, entityIds);
45
+ for (const { typename, id } of collectedEntities) {
46
+ let operationIds = entityToResponseIds.get(typename);
47
+ if (operationIds == null) {
48
+ operationIds = new Set();
49
+ entityToResponseIds.set(typename, operationIds);
50
+ }
51
+ // typename => operation
52
+ operationIds.add(responseId);
53
+ // operation => typename
54
+ entityIds.add(typename);
55
+ if (id !== undefined) {
56
+ const entityId = buildEntityId(typename, id);
57
+ let responseIds = entityToResponseIds.get(entityId);
58
+ if (responseIds == null) {
59
+ responseIds = new Set();
60
+ entityToResponseIds.set(entityId, responseIds);
61
+ }
62
+ // typename:id => operation
63
+ responseIds.add(responseId);
64
+ // operation => typename:id
65
+ entityIds.add(entityId);
66
+ }
67
+ }
68
+ },
69
+ get(responseId) {
70
+ var _a;
71
+ return (_a = cachedResponses.get(responseId)) !== null && _a !== void 0 ? _a : null;
72
+ },
73
+ invalidate(entitiesToRemove) {
74
+ for (const { typename, id } of entitiesToRemove) {
75
+ purgeEntity(id != null ? buildEntityId(typename, id) : typename);
76
+ }
77
+ },
78
+ };
79
+ };
80
+ export const defaultBuildEntityId = (typename, id) => `${typename}:${id}`;
package/esm/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './in-memory-cache.js';
2
+ export * from './plugin.js';
3
+ export * from './cache.js';
@@ -1,158 +1,16 @@
1
- import LRU from 'lru-cache';
2
1
  import { isAsyncIterable } from '@envelop/core';
3
- import { mapSchema, MapperKind } from '@graphql-tools/utils';
4
- import { print, defaultFieldResolver, TypeInfo, visit, visitWithTypeInfo } from 'graphql';
2
+ import { MapperKind, mapSchema } from '@graphql-tools/utils';
3
+ import { visit, TypeInfo, visitWithTypeInfo, defaultFieldResolver, } from 'graphql';
5
4
  import jsonStableStringify from 'fast-json-stable-stringify';
6
-
7
- const createInMemoryCache = (params) => {
8
- var _a;
9
- const buildEntityId = (_a = params === null || params === void 0 ? void 0 : params.buildEntityId) !== null && _a !== void 0 ? _a : defaultBuildEntityId;
10
- const cachedResponses = new LRU({
11
- max: params === null || params === void 0 ? void 0 : params.max,
12
- stale: false,
13
- noDisposeOnSet: true,
14
- dispose(responseId) {
15
- purgeResponse(responseId, false);
16
- },
17
- });
18
- const entityToResponseIds = new Map();
19
- const responseIdToEntityIds = new Map();
20
- function purgeResponse(responseId, shouldRemove = true) {
21
- var _a;
22
- const entityIds = responseIdToEntityIds.get(responseId);
23
- // get entities related to the response
24
- if (entityIds !== undefined) {
25
- for (const entityId of entityIds) {
26
- // remove the response mapping from the entity
27
- (_a = entityToResponseIds.get(entityId)) === null || _a === void 0 ? void 0 : _a.delete(responseId);
28
- }
29
- // remove all the entity mappings from the response
30
- responseIdToEntityIds.delete(responseId);
31
- }
32
- if (shouldRemove) {
33
- // remove the response from the cache
34
- cachedResponses.del(responseId);
35
- }
36
- }
37
- function purgeEntity(entity) {
38
- const responseIds = entityToResponseIds.get(entity);
39
- if (responseIds !== undefined) {
40
- for (const responseId of responseIds) {
41
- purgeResponse(responseId);
42
- }
43
- }
44
- }
45
- return {
46
- set(responseId, result, collectedEntities, ttl) {
47
- cachedResponses.set(responseId, result, ttl);
48
- const entityIds = new Set();
49
- responseIdToEntityIds.set(responseId, entityIds);
50
- for (const { typename, id } of collectedEntities) {
51
- let operationIds = entityToResponseIds.get(typename);
52
- if (operationIds == null) {
53
- operationIds = new Set();
54
- entityToResponseIds.set(typename, operationIds);
55
- }
56
- // typename => operation
57
- operationIds.add(responseId);
58
- // operation => typename
59
- entityIds.add(typename);
60
- if (id !== undefined) {
61
- const entityId = buildEntityId(typename, id);
62
- let responseIds = entityToResponseIds.get(entityId);
63
- if (responseIds == null) {
64
- responseIds = new Set();
65
- entityToResponseIds.set(entityId, responseIds);
66
- }
67
- // typename:id => operation
68
- responseIds.add(responseId);
69
- // operation => typename:id
70
- entityIds.add(entityId);
71
- }
72
- }
73
- },
74
- get(responseId) {
75
- var _a;
76
- return (_a = cachedResponses.get(responseId)) !== null && _a !== void 0 ? _a : null;
77
- },
78
- invalidate(entitiesToRemove) {
79
- for (const { typename, id } of entitiesToRemove) {
80
- purgeEntity(id != null ? buildEntityId(typename, id) : typename);
81
- }
82
- },
83
- };
84
- };
85
- const defaultBuildEntityId = (typename, id) => `${typename}:${id}`;
86
-
87
- const webCrypto = globalThis.crypto;
88
- const sha256Browser = (bytes) => {
89
- const hash = webCrypto.subtle.digest({ name: 'SHA-256' }, bytes);
90
- return new Promise((resolve, reject) => {
91
- Promise.resolve(hash)
92
- .then(function (result) {
93
- resolve(new Uint8Array(result));
94
- })
95
- .catch(function (error) {
96
- reject(error);
97
- });
98
- });
99
- };
100
- let nodeCrypto;
101
- if (typeof window === 'undefined' && !webCrypto) {
102
- // Indirect eval'd require/import to guarantee no side-effects in module scope
103
- // (optimization for minifier)
104
- try {
105
- // eslint-disable-next-line no-new-func
106
- nodeCrypto = Promise.resolve(new Function('require', 'return require("crypto")')(require));
107
- }
108
- catch (_error) {
109
- try {
110
- // eslint-disable-next-line no-new-func
111
- nodeCrypto = new Function('return import("crypto")')();
112
- }
113
- catch (_error) { }
114
- }
115
- }
116
- const hashSHA256 = async (text) => {
117
- // Node.js support
118
- if (nodeCrypto) {
119
- return nodeCrypto.then(crypto => crypto.createHash('sha256').update(text).digest('hex'));
120
- }
121
- if (webCrypto) {
122
- let buf;
123
- if (typeof TextEncoder !== 'undefined') {
124
- buf = new TextEncoder().encode(text);
125
- }
126
- else {
127
- buf = new Uint8Array(text.length);
128
- for (let i = 0, l = text.length; i < l; i++) {
129
- // NOTE: We assume that the input GraphQL Query only uses UTF-8 at most
130
- // since GraphQL mostly consists of ASCII, this is completely fine
131
- buf[i] = text.charCodeAt(i);
132
- }
133
- }
134
- const out = await sha256Browser(buf);
135
- let hash = '';
136
- for (let i = 0, l = out.length; i < l; i++) {
137
- const hex = out[i].toString(16);
138
- hash += '00'.slice(0, Math.max(0, 2 - hex.length)) + hex;
139
- }
140
- return hash;
141
- }
142
- if (process.env.NODE_ENV !== 'production') {
143
- // eslint-disable-next-line no-console
144
- console.warn('[@envelop/response-cache]: The Node Crypto and Web Crypto APIs are not available.\n' +
145
- 'This is an unexpected error. Please report it by filing a GitHub Issue.');
146
- }
147
- return Promise.resolve('');
148
- };
149
-
5
+ import { createInMemoryCache } from './in-memory-cache.js';
6
+ import { hashSHA256 } from './hashSHA256.js';
7
+ import { defaultGetDocumentString, useCacheDocumentString } from './cache-document-str.js';
150
8
  const contextSymbol = Symbol('responseCache');
151
9
  /**
152
10
  * Default function used for building the response cache key.
153
11
  * 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.
154
12
  */
155
- const defaultBuildResponseCacheKey = params => {
13
+ export const defaultBuildResponseCacheKey = params => {
156
14
  var _a, _b, _c;
157
15
  return hashSHA256([
158
16
  params.documentString,
@@ -169,7 +27,7 @@ const defaultBuildResponseCacheKey = params => {
169
27
  *
170
28
  * By default, results with errors (unexpected, EnvelopError, or GraphQLError) are not cached.
171
29
  */
172
- const defaultShouldCacheResult = (params) => {
30
+ export const defaultShouldCacheResult = (params) => {
173
31
  if (params.result.errors) {
174
32
  // eslint-disable-next-line no-console
175
33
  console.warn('[useResponseCache] Failed to cache due to errors');
@@ -177,16 +35,7 @@ const defaultShouldCacheResult = (params) => {
177
35
  }
178
36
  return true;
179
37
  };
180
- const documentStringByDocument = new WeakMap();
181
- function defaultGetDocumentString(executionArgs) {
182
- let documentString = documentStringByDocument.get(executionArgs.document);
183
- if (!documentString) {
184
- documentString = print(executionArgs.document);
185
- documentStringByDocument.set(executionArgs.document, documentString);
186
- }
187
- return documentString;
188
- }
189
- function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = defaultShouldCacheResult,
38
+ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = defaultShouldCacheResult,
190
39
  // eslint-disable-next-line dot-notation
191
40
  includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
192
41
  const appliedTransform = Symbol('responseCache.appliedTransform');
@@ -194,6 +43,11 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
194
43
  // never cache Introspections
195
44
  ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
196
45
  return {
46
+ onPluginInit({ addPlugin }) {
47
+ if (getDocumentString === defaultGetDocumentString) {
48
+ addPlugin(useCacheDocumentString());
49
+ }
50
+ },
197
51
  onSchemaChange({ schema, replaceSchema }) {
198
52
  var _a;
199
53
  // @ts-expect-error See https://github.com/graphql/graphql-js/pull/3511 - remove this comments once merged
@@ -207,13 +61,6 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
207
61
  };
208
62
  replaceSchema(patchedSchema);
209
63
  },
210
- onParse({ params: { source } }) {
211
- return function onParseEnd({ result }) {
212
- if (result != null && !(result instanceof Error)) {
213
- documentStringByDocument.set(result, source.toString());
214
- }
215
- };
216
- },
217
64
  async onExecute(ctx) {
218
65
  var _a;
219
66
  const identifier = new Map();
@@ -257,8 +104,9 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
257
104
  }
258
105
  const operationId = await buildResponseCacheKey({
259
106
  documentString: getDocumentString(ctx.args),
107
+ variableValues: ctx.args.variableValues,
108
+ operationName: ctx.args.operationName,
260
109
  sessionId: session(ctx.args.contextValue),
261
- ...ctx.args,
262
110
  });
263
111
  if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
264
112
  const cachedResponse = await cache.get(operationId);
@@ -398,5 +246,3 @@ function applyResponseCacheLogic(schema, idFieldNames) {
398
246
  },
399
247
  });
400
248
  }
401
-
402
- export { createInMemoryCache, defaultBuildEntityId, defaultBuildResponseCacheKey, defaultGetDocumentString, defaultShouldCacheResult, useResponseCache };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@envelop/response-cache",
3
- "version": "3.0.0-alpha-6a4e0e2.0",
3
+ "version": "3.0.0-alpha-e8a9318.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "@envelop/core": "^2.3.3",
6
+ "@envelop/core": "^2.4.0",
7
7
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
8
8
  },
9
9
  "dependencies": {
10
- "@graphql-tools/utils": "^8.6.1",
11
- "fast-json-stable-stringify": "^2.1.0",
12
- "lru-cache": "^6.0.0"
10
+ "@graphql-tools/utils": "^8.8.0",
11
+ "lru-cache": "^6.0.0",
12
+ "fast-json-stable-stringify": "^2.1.0"
13
13
  },
14
14
  "repository": {
15
15
  "type": "git",
@@ -18,21 +18,42 @@
18
18
  },
19
19
  "author": "Kamil Kisiela <kamilkisiela@gmail.com>",
20
20
  "license": "MIT",
21
- "main": "index.js",
22
- "module": "index.mjs",
23
- "typings": "index.d.ts",
21
+ "main": "cjs/index.js",
22
+ "module": "esm/index.js",
23
+ "typings": "typings/index.d.ts",
24
24
  "typescript": {
25
- "definition": "index.d.ts"
25
+ "definition": "typings/index.d.ts"
26
26
  },
27
+ "type": "module",
27
28
  "exports": {
28
29
  ".": {
29
- "require": "./index.js",
30
- "import": "./index.mjs"
30
+ "require": {
31
+ "types": "./typings/index.d.ts",
32
+ "default": "./cjs/index.js"
33
+ },
34
+ "import": {
35
+ "types": "./typings/index.d.ts",
36
+ "default": "./esm/index.js"
37
+ },
38
+ "default": {
39
+ "types": "./typings/index.d.ts",
40
+ "default": "./esm/index.js"
41
+ }
31
42
  },
32
43
  "./*": {
33
- "require": "./*.js",
34
- "import": "./*.mjs"
44
+ "require": {
45
+ "types": "./typings/*.d.ts",
46
+ "default": "./cjs/*.js"
47
+ },
48
+ "import": {
49
+ "types": "./typings/*.d.ts",
50
+ "default": "./esm/*.js"
51
+ },
52
+ "default": {
53
+ "types": "./typings/*.d.ts",
54
+ "default": "./esm/*.js"
55
+ }
35
56
  },
36
57
  "./package.json": "./package.json"
37
58
  }
38
- }
59
+ }
@@ -0,0 +1,4 @@
1
+ import { ExecutionArgs } from 'graphql';
2
+ import { Plugin } from '@envelop/core';
3
+ export declare function useCacheDocumentString(): Plugin;
4
+ export declare function defaultGetDocumentString(executionArgs: ExecutionArgs): string;
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- import type { Cache } from './cache';
1
+ import type { Cache } from './cache.js';
2
2
  export declare type BuildEntityId = (typename: string, id: number | string) => string;
3
3
  export declare type InMemoryCacheParameter = {
4
4
  /**
@@ -11,5 +11,5 @@ export declare type InMemoryCacheParameter = {
11
11
  */
12
12
  buildEntityId?: BuildEntityId;
13
13
  };
14
- export declare const createInMemoryCache: (params?: InMemoryCacheParameter | undefined) => Cache;
14
+ export declare const createInMemoryCache: (params?: InMemoryCacheParameter) => Cache;
15
15
  export declare const defaultBuildEntityId: BuildEntityId;
@@ -0,0 +1,3 @@
1
+ export * from './in-memory-cache.js';
2
+ export * from './plugin.js';
3
+ export * from './cache.js';
@@ -1,15 +1,19 @@
1
1
  import { Plugin, Maybe } from '@envelop/core';
2
2
  import { ExecutionArgs, ExecutionResult } from 'graphql';
3
- import type { Cache } from './cache';
3
+ import type { Cache } from './cache.js';
4
4
  /**
5
5
  * Function for building the response cache key based on the input parameters
6
6
  */
7
7
  export declare type BuildResponseCacheKeyFunction = (params: {
8
8
  /** Raw document string as sent from the client. */
9
9
  documentString: string;
10
+ /** Variable values as sent form the client. */
11
+ variableValues: ExecutionArgs['variableValues'];
12
+ /** The name of the GraphQL operation that should be executed from within the document. */
13
+ operationName?: Maybe<string>;
10
14
  /** optional sessionId for make unique cache keys based on the session. */
11
15
  sessionId?: Maybe<string>;
12
- } & ExecutionArgs) => Promise<string>;
16
+ }) => Promise<string>;
13
17
  export declare type GetDocumentStringFunction = (executionArgs: ExecutionArgs) => string;
14
18
  export declare type ShouldCacheResultFunction = (params: {
15
19
  result: ExecutionResult;
@@ -101,5 +105,4 @@ export declare const defaultBuildResponseCacheKey: BuildResponseCacheKeyFunction
101
105
  * By default, results with errors (unexpected, EnvelopError, or GraphQLError) are not cached.
102
106
  */
103
107
  export declare const defaultShouldCacheResult: ShouldCacheResultFunction;
104
- export declare function defaultGetDocumentString(executionArgs: ExecutionArgs): string;
105
108
  export declare function useResponseCache({ cache, ttl: globalTtl, session, enabled, ignoredTypes, ttlPerType, ttlPerSchemaCoordinate, idFields, invalidateViaMutation, buildResponseCacheKey, getDocumentString, shouldCacheResult, includeExtensionMetadata, }?: UseResponseCacheParameter): Plugin;
package/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './in-memory-cache';
2
- export * from './plugin';
3
- export * from './cache';