@envelop/response-cache 8.2.0-alpha-20251013081815-d6f74fceb1a32fd336b2664f90f87872e1bbf2fe → 10.0.0-alpha-20251211205538-5005189af5ce4a47150dc63ed3732eee84998197

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/cjs/plugin.js CHANGED
@@ -233,9 +233,11 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
233
233
  }
234
234
  }
235
235
  function invalidateCache(result, setResult) {
236
+ let changed = false;
236
237
  if (result.data) {
237
- collectEntities(result.data, onEntity);
238
- setStringifyWithoutMetadata(result);
238
+ result = { ...result };
239
+ result.data = removeMetadataFieldsFromResult(result.data, onEntity);
240
+ changed = true;
239
241
  }
240
242
  const cacheInstance = cacheFactory(onExecuteParams.args.contextValue);
241
243
  if (cacheInstance == null) {
@@ -251,6 +253,9 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
251
253
  }));
252
254
  }
253
255
  }
256
+ if (changed) {
257
+ setResult(result);
258
+ }
254
259
  }
255
260
  if (invalidateViaMutation !== false) {
256
261
  const operationAST = (0, graphql_1.getOperationAST)(onExecuteParams.args.document, onExecuteParams.args.operationName);
@@ -291,8 +296,7 @@ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache
291
296
  }
292
297
  function maybeCacheResult(result, setResult) {
293
298
  if (result.data) {
294
- collectEntities(result.data, onEntity);
295
- setStringifyWithoutMetadata(result);
299
+ result.data = removeMetadataFieldsFromResult(result.data, onEntity);
296
300
  }
297
301
  // we only use the global ttl if no currentTtl has been determined.
298
302
  let finalTtl = currentTtl ?? globalTtl;
@@ -340,7 +344,7 @@ function handleAsyncIterableResult(handler) {
340
344
  onNext(payload) {
341
345
  // This is the first result with the initial data payload sent to the client. We use it as the base result
342
346
  if (payload.result.data) {
343
- result.data = structuredClone(payload.result.data);
347
+ result.data = payload.result.data;
344
348
  }
345
349
  if (payload.result.errors) {
346
350
  result.errors = payload.result.errors;
@@ -352,10 +356,7 @@ function handleAsyncIterableResult(handler) {
352
356
  const { incremental, hasNext } = payload.result;
353
357
  if (incremental) {
354
358
  for (const patch of incremental) {
355
- (0, utils_1.mergeIncrementalResult)({
356
- executionResult: result,
357
- incrementalResult: structuredClone(patch),
358
- });
359
+ (0, utils_1.mergeIncrementalResult)({ executionResult: result, incrementalResult: patch });
359
360
  }
360
361
  }
361
362
  if (!hasNext) {
@@ -363,22 +364,30 @@ function handleAsyncIterableResult(handler) {
363
364
  handler(result, payload.setResult);
364
365
  }
365
366
  }
366
- if (payload.result.data) {
367
- collectEntities(payload.result.data);
367
+ const newResult = { ...payload.result };
368
+ // Handle initial/single result
369
+ if (newResult.data) {
370
+ newResult.data = removeMetadataFieldsFromResult(newResult.data);
368
371
  }
369
372
  // Handle Incremental results
370
- if ('hasNext' in payload.result && payload.result.incremental) {
371
- payload.result.incremental = payload.result.incremental.map(value => {
373
+ if ('hasNext' in newResult && newResult.incremental) {
374
+ newResult.incremental = newResult.incremental.map(value => {
372
375
  if ('items' in value && value.items) {
373
- collectEntities(value.items);
376
+ return {
377
+ ...value,
378
+ items: removeMetadataFieldsFromResult(value.items),
379
+ };
374
380
  }
375
381
  if ('data' in value && value.data) {
376
- collectEntities(value.data);
382
+ return {
383
+ ...value,
384
+ data: removeMetadataFieldsFromResult(value.data),
385
+ };
377
386
  }
378
387
  return value;
379
388
  });
380
389
  }
381
- setStringifyWithoutMetadata(payload.result);
390
+ payload.setResult(newResult);
382
391
  },
383
392
  };
384
393
  }
@@ -423,45 +432,38 @@ exports.cacheControlDirective = `
423
432
 
424
433
  directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT
425
434
  `;
426
- function collectEntities(data, onEntity) {
435
+ function removeMetadataFieldsFromResult(data, onEntity) {
427
436
  if (Array.isArray(data)) {
428
- for (const record of data) {
429
- collectEntities(record, onEntity);
430
- }
431
- return;
437
+ return data.map(record => removeMetadataFieldsFromResult(record, onEntity));
432
438
  }
433
439
  if (typeof data !== 'object' || data == null) {
434
- return;
440
+ return data;
435
441
  }
436
442
  const dataPrototype = Object.getPrototypeOf(data);
437
- // TODO: For some reason, when running in Jest, `structuredClone` result have a weird prototype
438
- // that doesn't equal Object.prototype as it should.
439
- // As a workaround, we can just check that there is no parent prototype, which should be
440
- // the case only when it's the Object prototype
441
- // Rollback once migrated to Bun or vitest
442
- //
443
- // if (dataPrototype != null && dataPrototype !== Object.prototype) {
444
- if (dataPrototype != null && Object.getPrototypeOf(dataPrototype) !== null) {
445
- // It is not a plain object, like a Date, don't inspect further
446
- return;
443
+ if (dataPrototype != null && dataPrototype !== Object.prototype) {
444
+ return data;
447
445
  }
446
+ // clone the data to avoid mutation
447
+ data = { ...data };
448
448
  const typename = data.__responseCacheTypeName ?? data.__typename;
449
449
  if (typeof typename === 'string') {
450
450
  const entity = { typename };
451
+ delete data.__responseCacheTypeName;
451
452
  if (data.__responseCacheId &&
452
453
  (typeof data.__responseCacheId === 'string' || typeof data.__responseCacheId === 'number')) {
453
454
  entity.id = data.__responseCacheId;
455
+ delete data.__responseCacheId;
454
456
  }
455
457
  onEntity?.(entity, data);
456
458
  }
457
459
  for (const key in data) {
458
- collectEntities(data[key], onEntity);
460
+ const value = data[key];
461
+ if (Array.isArray(value)) {
462
+ data[key] = removeMetadataFieldsFromResult(value, onEntity);
463
+ }
464
+ if (value !== null && typeof value === 'object') {
465
+ data[key] = removeMetadataFieldsFromResult(value, onEntity);
466
+ }
459
467
  }
468
+ return data;
460
469
  }
461
- function setStringifyWithoutMetadata(result) {
462
- result.stringify = stringifyWithoutMetadata;
463
- return result;
464
- }
465
- const stringifyWithoutMetadata = result => {
466
- return JSON.stringify(result, (key, value) => key === '__responseCacheId' || key === '__responseCacheTypeName' ? undefined : value);
467
- };
package/esm/plugin.js CHANGED
@@ -224,9 +224,11 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
224
224
  }
225
225
  }
226
226
  function invalidateCache(result, setResult) {
227
+ let changed = false;
227
228
  if (result.data) {
228
- collectEntities(result.data, onEntity);
229
- setStringifyWithoutMetadata(result);
229
+ result = { ...result };
230
+ result.data = removeMetadataFieldsFromResult(result.data, onEntity);
231
+ changed = true;
230
232
  }
231
233
  const cacheInstance = cacheFactory(onExecuteParams.args.contextValue);
232
234
  if (cacheInstance == null) {
@@ -242,6 +244,9 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
242
244
  }));
243
245
  }
244
246
  }
247
+ if (changed) {
248
+ setResult(result);
249
+ }
245
250
  }
246
251
  if (invalidateViaMutation !== false) {
247
252
  const operationAST = getOperationAST(onExecuteParams.args.document, onExecuteParams.args.operationName);
@@ -282,8 +287,7 @@ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl
282
287
  }
283
288
  function maybeCacheResult(result, setResult) {
284
289
  if (result.data) {
285
- collectEntities(result.data, onEntity);
286
- setStringifyWithoutMetadata(result);
290
+ result.data = removeMetadataFieldsFromResult(result.data, onEntity);
287
291
  }
288
292
  // we only use the global ttl if no currentTtl has been determined.
289
293
  let finalTtl = currentTtl ?? globalTtl;
@@ -331,7 +335,7 @@ function handleAsyncIterableResult(handler) {
331
335
  onNext(payload) {
332
336
  // This is the first result with the initial data payload sent to the client. We use it as the base result
333
337
  if (payload.result.data) {
334
- result.data = structuredClone(payload.result.data);
338
+ result.data = payload.result.data;
335
339
  }
336
340
  if (payload.result.errors) {
337
341
  result.errors = payload.result.errors;
@@ -343,10 +347,7 @@ function handleAsyncIterableResult(handler) {
343
347
  const { incremental, hasNext } = payload.result;
344
348
  if (incremental) {
345
349
  for (const patch of incremental) {
346
- mergeIncrementalResult({
347
- executionResult: result,
348
- incrementalResult: structuredClone(patch),
349
- });
350
+ mergeIncrementalResult({ executionResult: result, incrementalResult: patch });
350
351
  }
351
352
  }
352
353
  if (!hasNext) {
@@ -354,22 +355,30 @@ function handleAsyncIterableResult(handler) {
354
355
  handler(result, payload.setResult);
355
356
  }
356
357
  }
357
- if (payload.result.data) {
358
- collectEntities(payload.result.data);
358
+ const newResult = { ...payload.result };
359
+ // Handle initial/single result
360
+ if (newResult.data) {
361
+ newResult.data = removeMetadataFieldsFromResult(newResult.data);
359
362
  }
360
363
  // Handle Incremental results
361
- if ('hasNext' in payload.result && payload.result.incremental) {
362
- payload.result.incremental = payload.result.incremental.map(value => {
364
+ if ('hasNext' in newResult && newResult.incremental) {
365
+ newResult.incremental = newResult.incremental.map(value => {
363
366
  if ('items' in value && value.items) {
364
- collectEntities(value.items);
367
+ return {
368
+ ...value,
369
+ items: removeMetadataFieldsFromResult(value.items),
370
+ };
365
371
  }
366
372
  if ('data' in value && value.data) {
367
- collectEntities(value.data);
373
+ return {
374
+ ...value,
375
+ data: removeMetadataFieldsFromResult(value.data),
376
+ };
368
377
  }
369
378
  return value;
370
379
  });
371
380
  }
372
- setStringifyWithoutMetadata(payload.result);
381
+ payload.setResult(newResult);
373
382
  },
374
383
  };
375
384
  }
@@ -414,45 +423,38 @@ export const cacheControlDirective = /* GraphQL */ `
414
423
 
415
424
  directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT
416
425
  `;
417
- function collectEntities(data, onEntity) {
426
+ function removeMetadataFieldsFromResult(data, onEntity) {
418
427
  if (Array.isArray(data)) {
419
- for (const record of data) {
420
- collectEntities(record, onEntity);
421
- }
422
- return;
428
+ return data.map(record => removeMetadataFieldsFromResult(record, onEntity));
423
429
  }
424
430
  if (typeof data !== 'object' || data == null) {
425
- return;
431
+ return data;
426
432
  }
427
433
  const dataPrototype = Object.getPrototypeOf(data);
428
- // TODO: For some reason, when running in Jest, `structuredClone` result have a weird prototype
429
- // that doesn't equal Object.prototype as it should.
430
- // As a workaround, we can just check that there is no parent prototype, which should be
431
- // the case only when it's the Object prototype
432
- // Rollback once migrated to Bun or vitest
433
- //
434
- // if (dataPrototype != null && dataPrototype !== Object.prototype) {
435
- if (dataPrototype != null && Object.getPrototypeOf(dataPrototype) !== null) {
436
- // It is not a plain object, like a Date, don't inspect further
437
- return;
434
+ if (dataPrototype != null && dataPrototype !== Object.prototype) {
435
+ return data;
438
436
  }
437
+ // clone the data to avoid mutation
438
+ data = { ...data };
439
439
  const typename = data.__responseCacheTypeName ?? data.__typename;
440
440
  if (typeof typename === 'string') {
441
441
  const entity = { typename };
442
+ delete data.__responseCacheTypeName;
442
443
  if (data.__responseCacheId &&
443
444
  (typeof data.__responseCacheId === 'string' || typeof data.__responseCacheId === 'number')) {
444
445
  entity.id = data.__responseCacheId;
446
+ delete data.__responseCacheId;
445
447
  }
446
448
  onEntity?.(entity, data);
447
449
  }
448
450
  for (const key in data) {
449
- collectEntities(data[key], onEntity);
451
+ const value = data[key];
452
+ if (Array.isArray(value)) {
453
+ data[key] = removeMetadataFieldsFromResult(value, onEntity);
454
+ }
455
+ if (value !== null && typeof value === 'object') {
456
+ data[key] = removeMetadataFieldsFromResult(value, onEntity);
457
+ }
450
458
  }
459
+ return data;
451
460
  }
452
- function setStringifyWithoutMetadata(result) {
453
- result.stringify = stringifyWithoutMetadata;
454
- return result;
455
- }
456
- const stringifyWithoutMetadata = result => {
457
- return JSON.stringify(result, (key, value) => key === '__responseCacheId' || key === '__responseCacheTypeName' ? undefined : value);
458
- };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@envelop/response-cache",
3
- "version": "8.2.0-alpha-20251013081815-d6f74fceb1a32fd336b2664f90f87872e1bbf2fe",
3
+ "version": "10.0.0-alpha-20251211205538-5005189af5ce4a47150dc63ed3732eee84998197",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
7
- "@envelop/core": "^5.3.2"
7
+ "@envelop/core": "^5.5.0-alpha-20251211205538-5005189af5ce4a47150dc63ed3732eee84998197"
8
8
  },
9
9
  "dependencies": {
10
10
  "@graphql-tools/utils": "^10.0.3",