@leejpsd/nextjs-cache-handler 0.2.0 → 0.3.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/README.md +47 -25
  3. package/dist/cache-components/index.cjs +235 -46
  4. package/dist/cache-components/index.cjs.map +1 -1
  5. package/dist/cache-components/index.d.cts +1 -1
  6. package/dist/cache-components/index.d.ts +1 -1
  7. package/dist/cache-components/index.js +227 -46
  8. package/dist/cache-components/index.js.map +1 -1
  9. package/dist/incremental/index.cjs +231 -41
  10. package/dist/incremental/index.cjs.map +1 -1
  11. package/dist/incremental/index.d.cts +42 -3
  12. package/dist/incremental/index.d.ts +42 -3
  13. package/dist/incremental/index.js +223 -41
  14. package/dist/incremental/index.js.map +1 -1
  15. package/dist/index.cjs +313 -70
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +305 -70
  20. package/dist/index.js.map +1 -1
  21. package/dist/ops/index.d.cts +1 -1
  22. package/dist/ops/index.d.ts +1 -1
  23. package/dist/otel/index.cjs +72 -0
  24. package/dist/otel/index.cjs.map +1 -0
  25. package/dist/otel/index.d.cts +72 -0
  26. package/dist/otel/index.d.ts +72 -0
  27. package/dist/otel/index.js +64 -0
  28. package/dist/otel/index.js.map +1 -0
  29. package/dist/shared/client/adapter-ioredis.cjs +49 -10
  30. package/dist/shared/client/adapter-ioredis.cjs.map +1 -1
  31. package/dist/shared/client/adapter-ioredis.d.cts +5 -2
  32. package/dist/shared/client/adapter-ioredis.d.ts +5 -2
  33. package/dist/shared/client/adapter-ioredis.js +43 -11
  34. package/dist/shared/client/adapter-ioredis.js.map +1 -1
  35. package/dist/shared/client/adapter-redis.cjs +22 -7
  36. package/dist/shared/client/adapter-redis.cjs.map +1 -1
  37. package/dist/shared/client/adapter-redis.d.cts +1 -1
  38. package/dist/shared/client/adapter-redis.d.ts +1 -1
  39. package/dist/shared/client/adapter-redis.js +16 -7
  40. package/dist/shared/client/adapter-redis.js.map +1 -1
  41. package/dist/{types-Dq1q6qXd.d.cts → types-Bl-rkLKX.d.cts} +37 -1
  42. package/dist/{types-Dq1q6qXd.d.ts → types-Bl-rkLKX.d.ts} +37 -1
  43. package/package.json +20 -3
@@ -1,13 +1,16 @@
1
1
  'use strict';
2
2
 
3
+ var module$1 = require('module');
4
+ var path = require('path');
5
+ var util = require('util');
6
+ var zlib = require('zlib');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var path__default = /*#__PURE__*/_interopDefault(path);
11
+
3
12
  var __defProp = Object.defineProperty;
4
13
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
- }) : x)(function(x) {
8
- if (typeof require !== "undefined") return require.apply(this, arguments);
9
- throw Error('Dynamic require of "' + x + '" is not supported');
10
- });
11
14
  var __esm = (fn, res) => function __init() {
12
15
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
16
  };
@@ -16,6 +19,32 @@ var __export = (target, all) => {
16
19
  __defProp(target, name, { get: all[name], enumerable: true });
17
20
  };
18
21
 
22
+ // node_modules/tsup/assets/cjs_shims.js
23
+ var getImportMetaUrl, importMetaUrl;
24
+ var init_cjs_shims = __esm({
25
+ "node_modules/tsup/assets/cjs_shims.js"() {
26
+ getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
27
+ importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
28
+ }
29
+ });
30
+ function nodeRequire(id) {
31
+ try {
32
+ return packageRequire(id);
33
+ } catch {
34
+ return cwdRequire(id);
35
+ }
36
+ }
37
+ var packageRequire, cwdRequire;
38
+ var init_node_require = __esm({
39
+ "src/shared/node-require.ts"() {
40
+ init_cjs_shims();
41
+ packageRequire = module$1.createRequire(importMetaUrl);
42
+ cwdRequire = module$1.createRequire(
43
+ path__default.default.join(process.cwd(), "__resolve-anchor__.js")
44
+ );
45
+ }
46
+ });
47
+
19
48
  // src/shared/client/adapter-redis.ts
20
49
  var adapter_redis_exports = {};
21
50
  __export(adapter_redis_exports, {
@@ -26,7 +55,7 @@ function adaptRedisV5(client) {
26
55
  return client;
27
56
  }
28
57
  function createRedisV5Client(config) {
29
- const { createClient } = __require("redis");
58
+ const { createClient } = nodeRequire("redis");
30
59
  const useTls = config.tls ?? config.url.startsWith("rediss://");
31
60
  const opts = {
32
61
  url: config.url,
@@ -41,6 +70,8 @@ function createRedisV5Client(config) {
41
70
  }
42
71
  var init_adapter_redis = __esm({
43
72
  "src/shared/client/adapter-redis.ts"() {
73
+ init_cjs_shims();
74
+ init_node_require();
44
75
  }
45
76
  });
46
77
 
@@ -50,7 +81,8 @@ __export(adapter_ioredis_exports, {
50
81
  adaptCluster: () => adaptCluster,
51
82
  adaptIoredis: () => adaptIoredis,
52
83
  createIoredisClient: () => createIoredisClient,
53
- createIoredisCluster: () => createIoredisCluster
84
+ createIoredisCluster: () => createIoredisCluster,
85
+ createIoredisSentinel: () => createIoredisSentinel
54
86
  });
55
87
  function adapt(client, isClusterClient = false) {
56
88
  return {
@@ -133,6 +165,9 @@ function adapt(client, isClusterClient = false) {
133
165
  on: (event, listener) => {
134
166
  client.on(event, listener);
135
167
  return client;
168
+ },
169
+ dispose: () => {
170
+ client.disconnect();
136
171
  }
137
172
  };
138
173
  }
@@ -143,18 +178,36 @@ function adaptCluster(cluster) {
143
178
  return adapt(cluster, true);
144
179
  }
145
180
  function createIoredisClient(config) {
146
- const IORedis = __require("ioredis");
181
+ const IORedis = nodeRequire("ioredis");
147
182
  const Ctor = IORedis.default ?? IORedis;
148
183
  return new Ctor(config.url, {
149
184
  password: config.password,
150
185
  connectTimeout: config.connectTimeout ?? 1500,
151
186
  tls: config.tls ? {} : void 0,
152
187
  lazyConnect: true,
153
- maxRetriesPerRequest: 1
188
+ maxRetriesPerRequest: 1,
189
+ // Batch same-tick commands into one RTT (matches redis@5's default
190
+ // behavior) — makes Promise.all fan-outs a single pipeline.
191
+ enableAutoPipelining: true
192
+ });
193
+ }
194
+ function createIoredisSentinel(config) {
195
+ const IORedis = nodeRequire("ioredis");
196
+ const Ctor = IORedis.default ?? IORedis;
197
+ return new Ctor({
198
+ sentinels: config.sentinels,
199
+ name: config.name,
200
+ password: config.password,
201
+ sentinelPassword: config.sentinelPassword,
202
+ connectTimeout: config.connectTimeout ?? 1500,
203
+ tls: config.tls ? {} : void 0,
204
+ lazyConnect: true,
205
+ maxRetriesPerRequest: 1,
206
+ enableAutoPipelining: true
154
207
  });
155
208
  }
156
209
  function createIoredisCluster(config) {
157
- const IORedis = __require("ioredis");
210
+ const IORedis = nodeRequire("ioredis");
158
211
  const ClusterCtor = IORedis.Cluster ?? IORedis.default.Cluster;
159
212
  return new ClusterCtor(config.nodes, {
160
213
  redisOptions: {
@@ -162,15 +215,26 @@ function createIoredisCluster(config) {
162
215
  tls: config.tls ? {} : void 0
163
216
  },
164
217
  enableReadyCheck: true,
165
- lazyConnect: true
218
+ lazyConnect: true,
219
+ // Same-slot commands issued in the same tick share one pipeline.
220
+ enableAutoPipelining: true
166
221
  });
167
222
  }
168
223
  var init_adapter_ioredis = __esm({
169
224
  "src/shared/client/adapter-ioredis.ts"() {
225
+ init_cjs_shims();
226
+ init_node_require();
170
227
  }
171
228
  });
172
229
 
230
+ // src/incremental/index.ts
231
+ init_cjs_shims();
232
+
233
+ // src/incremental/handler.ts
234
+ init_cjs_shims();
235
+
173
236
  // src/shared/abort.ts
237
+ init_cjs_shims();
174
238
  var CacheTimeoutError = class extends Error {
175
239
  constructor(opName, ms) {
176
240
  super(`[@leejpsd/nextjs-cache-handler] ${opName} timed out after ${ms}ms`);
@@ -202,7 +266,41 @@ async function withAbortSignal(opName, ms, fn) {
202
266
  }
203
267
  }
204
268
 
269
+ // src/shared/compress.ts
270
+ init_cjs_shims();
271
+ var gzipAsync = util.promisify(zlib.gzip);
272
+ var gunzipAsync = util.promisify(zlib.gunzip);
273
+ var brotliCompressAsync = util.promisify(zlib.brotliCompress);
274
+ var brotliDecompressAsync = util.promisify(zlib.brotliDecompress);
275
+ var GZIP_MARKER = "__ncgz__:";
276
+ var BROTLI_MARKER = "__ncbr__:";
277
+ var MIN_COMPRESS_BYTES = 1024;
278
+ var BROTLI_OPTS = {
279
+ params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 }
280
+ };
281
+ async function compressValue(value, algo) {
282
+ if (!algo || Buffer.byteLength(value) < MIN_COMPRESS_BYTES) return value;
283
+ const input = Buffer.from(value);
284
+ if (algo === "gzip") {
285
+ return GZIP_MARKER + (await gzipAsync(input)).toString("base64");
286
+ }
287
+ return BROTLI_MARKER + (await brotliCompressAsync(input, BROTLI_OPTS)).toString("base64");
288
+ }
289
+ async function decompressValue(value) {
290
+ if (value === null) return null;
291
+ if (value.startsWith(GZIP_MARKER)) {
292
+ const buf = Buffer.from(value.slice(GZIP_MARKER.length), "base64");
293
+ return (await gunzipAsync(buf)).toString("utf8");
294
+ }
295
+ if (value.startsWith(BROTLI_MARKER)) {
296
+ const buf = Buffer.from(value.slice(BROTLI_MARKER.length), "base64");
297
+ return (await brotliDecompressAsync(buf)).toString("utf8");
298
+ }
299
+ return value;
300
+ }
301
+
205
302
  // src/shared/client/index.ts
303
+ init_cjs_shims();
206
304
  async function buildClient(source) {
207
305
  if (typeof source === "function") {
208
306
  return await source();
@@ -220,6 +318,10 @@ async function buildClient(source) {
220
318
  const { adaptCluster: adaptCluster2, createIoredisCluster: createIoredisCluster2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
221
319
  return adaptCluster2(createIoredisCluster2(source));
222
320
  }
321
+ case "sentinel": {
322
+ const { adaptIoredis: adaptIoredis2, createIoredisSentinel: createIoredisSentinel2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
323
+ return adaptIoredis2(createIoredisSentinel2(source));
324
+ }
223
325
  default: {
224
326
  const exhaustive = source;
225
327
  throw new Error(
@@ -229,28 +331,45 @@ async function buildClient(source) {
229
331
  }
230
332
  }
231
333
  var ConnectionManager = class {
232
- constructor(source, onError) {
334
+ constructor(source, onError, retry = {}) {
233
335
  this.source = source;
234
336
  this.onError = onError;
337
+ this.baseCooldownMs = retry.baseCooldownMs ?? 1e3;
338
+ this.maxCooldownMs = retry.maxCooldownMs ?? 3e4;
235
339
  }
236
340
  source;
237
341
  onError;
238
342
  client = null;
239
343
  connecting = null;
240
- failed = false;
344
+ consecutiveFailures = 0;
345
+ lastFailureAt = 0;
346
+ baseCooldownMs;
347
+ maxCooldownMs;
348
+ cooldownMs() {
349
+ if (this.consecutiveFailures === 0) return 0;
350
+ return Math.min(
351
+ this.maxCooldownMs,
352
+ this.baseCooldownMs * 2 ** (this.consecutiveFailures - 1)
353
+ );
354
+ }
241
355
  async getOrConnect() {
242
356
  if (this.client?.isOpen) return this.client;
243
- if (this.failed) return null;
244
357
  if (this.connecting) return this.connecting;
358
+ if (Date.now() - this.lastFailureAt < this.cooldownMs()) return null;
359
+ const stale = this.client;
360
+ this.client = null;
245
361
  this.connecting = (async () => {
362
+ disposeQuietly(stale);
246
363
  try {
247
364
  const c = await buildClient(this.source);
248
365
  c.on("error", (err) => this.onError(err));
249
366
  if (!c.isOpen) await c.connect();
250
367
  this.client = c;
368
+ this.consecutiveFailures = 0;
251
369
  return c;
252
370
  } catch (err) {
253
- this.failed = true;
371
+ this.consecutiveFailures += 1;
372
+ this.lastFailureAt = Date.now();
254
373
  this.onError(err);
255
374
  return null;
256
375
  } finally {
@@ -260,13 +379,24 @@ var ConnectionManager = class {
260
379
  return this.connecting;
261
380
  }
262
381
  reset() {
382
+ disposeQuietly(this.client);
263
383
  this.client = null;
264
384
  this.connecting = null;
265
- this.failed = false;
385
+ this.consecutiveFailures = 0;
386
+ this.lastFailureAt = 0;
266
387
  }
267
388
  };
389
+ function disposeQuietly(client) {
390
+ if (!client) return;
391
+ const c = client;
392
+ try {
393
+ (c.dispose ?? c.destroy ?? c.disconnect)?.call(c);
394
+ } catch {
395
+ }
396
+ }
268
397
 
269
398
  // src/shared/logger.ts
399
+ init_cjs_shims();
270
400
  var defaultLogger = {
271
401
  debug: () => {
272
402
  },
@@ -283,13 +413,19 @@ var defaultLogger = {
283
413
  };
284
414
 
285
415
  // src/shared/memory-fallback.ts
416
+ init_cjs_shims();
286
417
  var MAX_TIMER_MS = 2147483647;
287
418
  function clampTimerMs(ms) {
288
419
  return Math.min(Math.max(0, ms), MAX_TIMER_MS);
289
420
  }
421
+ var DEFAULT_MAX_ENTRIES = 1e3;
290
422
  var MemoryStore = class {
291
423
  kv = /* @__PURE__ */ new Map();
292
424
  timers = /* @__PURE__ */ new Map();
425
+ maxEntries;
426
+ constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
427
+ this.maxEntries = Math.max(1, maxEntries);
428
+ }
293
429
  set(key, value, ttlSeconds) {
294
430
  this.delete(key);
295
431
  const expiresAt = Date.now() + ttlSeconds * 1e3;
@@ -299,6 +435,10 @@ var MemoryStore = class {
299
435
  timer.unref();
300
436
  }
301
437
  this.timers.set(key, timer);
438
+ while (this.kv.size > this.maxEntries) {
439
+ const oldest = this.kv.keys().next().value;
440
+ this.delete(oldest);
441
+ }
302
442
  }
303
443
  get(key) {
304
444
  const entry = this.kv.get(key);
@@ -307,6 +447,8 @@ var MemoryStore = class {
307
447
  this.delete(key);
308
448
  return null;
309
449
  }
450
+ this.kv.delete(key);
451
+ this.kv.set(key, entry);
310
452
  return entry.value;
311
453
  }
312
454
  delete(key) {
@@ -339,6 +481,7 @@ var MemoryStore = class {
339
481
  };
340
482
 
341
483
  // src/shared/metrics.ts
484
+ init_cjs_shims();
342
485
  var counters = /* @__PURE__ */ new Map();
343
486
  function bump(event) {
344
487
  const existing = counters.get(event.type);
@@ -367,6 +510,7 @@ function createEmitter(onMetric) {
367
510
  }
368
511
 
369
512
  // src/shared/namespace.ts
513
+ init_cjs_shims();
370
514
  var FALLBACK = "unversioned";
371
515
  function resolveBuildNamespace(override) {
372
516
  if (override !== void 0) {
@@ -380,6 +524,7 @@ function buildKey(prefix, namespace, rest, hashTag = false) {
380
524
  }
381
525
 
382
526
  // src/shared/build-phase.ts
527
+ init_cjs_shims();
383
528
  var PHASE_PRODUCTION_BUILD = "phase-production-build";
384
529
  function isBuildPhase() {
385
530
  return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD;
@@ -392,6 +537,7 @@ function shouldUseRedis(opts) {
392
537
  }
393
538
 
394
539
  // src/incremental/serialize.ts
540
+ init_cjs_shims();
395
541
  function isEnvelope(value) {
396
542
  return typeof value === "object" && value !== null && "__type" in value && typeof value.__type === "string";
397
543
  }
@@ -473,7 +619,7 @@ function init(opts) {
473
619
  logger,
474
620
  emit,
475
621
  conn,
476
- memEntries: new MemoryStore(),
622
+ memEntries: new MemoryStore(opts.memoryMaxEntries),
477
623
  memTagStates: /* @__PURE__ */ new Map(),
478
624
  resolveNs: () => resolveBuildNamespace(opts.buildNamespace)
479
625
  };
@@ -532,7 +678,7 @@ function normalizeTtlSeconds(data, ctx) {
532
678
  return ONE_YEAR_SEC;
533
679
  }
534
680
  }
535
- const revalidate = ctx?.cacheControl?.revalidate;
681
+ const revalidate = ctx?.cacheControl?.revalidate ?? ctx?.revalidate;
536
682
  if (revalidate === false || revalidate === void 0) return ONE_YEAR_SEC;
537
683
  if (typeof revalidate === "number" && revalidate > 0) {
538
684
  return Math.min(Math.max(Math.ceil(revalidate), MIN_TTL_SEC), ONE_YEAR_SEC);
@@ -570,10 +716,49 @@ async function readTagStates(state, tags) {
570
716
  var IncrementalRedisCacheHandler = class {
571
717
  state;
572
718
  revalidatedTags;
719
+ /**
720
+ * Request-scoped memo of Redis entry reads. Next.js instantiates this class
721
+ * per request (and calls resetRequestCache() at request boundaries when it
722
+ * reuses an instance), so entries live at most one request. Bounded by the
723
+ * number of distinct cache keys a single request touches.
724
+ */
725
+ requestMemo = /* @__PURE__ */ new Map();
573
726
  constructor(state, ctx) {
574
727
  this.state = state;
575
728
  this.revalidatedTags = normalizeTags(ctx?.revalidatedTags);
576
729
  }
730
+ /**
731
+ * Redis entry read, deduplicated per request: parallel RSC renders or
732
+ * repeated fetch() calls hitting the same cache key share one GET. The
733
+ * memoized promise never rejects — errors are converted to a miss (with
734
+ * the same timeout metric the inline path emitted before).
735
+ */
736
+ readEntryDeduped(eKey) {
737
+ const memoized = this.requestMemo.get(eKey);
738
+ if (memoized) {
739
+ this.state.emit({ type: "cache.get.deduped" });
740
+ return memoized;
741
+ }
742
+ const read = withAbortSignal(
743
+ "incremental.get",
744
+ this.state.opts.abortTimeoutMs,
745
+ async () => {
746
+ const client = await this.state.conn.getOrConnect();
747
+ if (!client) return null;
748
+ return await decompressValue(await client.get(eKey));
749
+ }
750
+ ).catch((err) => {
751
+ if (err instanceof CacheTimeoutError) {
752
+ this.state.emit({
753
+ type: "redis.timeout",
754
+ meta: { op: "incremental.get" }
755
+ });
756
+ }
757
+ return null;
758
+ });
759
+ this.requestMemo.set(eKey, read);
760
+ return read;
761
+ }
577
762
  async get(cacheKey, ctx) {
578
763
  try {
579
764
  const requestedTags = normalizeTags([
@@ -585,24 +770,11 @@ var IncrementalRedisCacheHandler = class {
585
770
  const useRedis = !useLocal && gateRedis(this.state);
586
771
  let raw = null;
587
772
  if (useRedis) {
588
- try {
589
- raw = await withAbortSignal(
590
- "incremental.get",
591
- this.state.opts.abortTimeoutMs,
592
- async () => {
593
- const client = await this.state.conn.getOrConnect();
594
- if (!client) return null;
595
- return await client.get(eKey);
596
- }
597
- );
598
- } catch (err) {
599
- if (err instanceof CacheTimeoutError) {
600
- this.state.emit({ type: "redis.timeout", meta: { op: "incremental.get" } });
601
- }
602
- raw = null;
603
- }
773
+ raw = await this.readEntryDeduped(eKey);
774
+ }
775
+ if (raw === null && (useLocal || this.state.opts.fallback !== "never")) {
776
+ raw = this.state.memEntries.get(eKey);
604
777
  }
605
- if (raw === null) raw = this.state.memEntries.get(eKey);
606
778
  if (raw === null) {
607
779
  this.state.emit({ type: "cache.miss" });
608
780
  return null;
@@ -615,7 +787,7 @@ var IncrementalRedisCacheHandler = class {
615
787
  return null;
616
788
  }
617
789
  const tagStates = await readTagStates(this.state, tags);
618
- const isFetch = ctx?.kind === "FETCH";
790
+ const isFetch = ctx?.kind === "FETCH" || ctx?.kindHint?.toLowerCase() === "fetch" || typeof parsed.value === "object" && parsed.value !== null && parsed.value.kind === "FETCH";
619
791
  if (tagStates.some((s) => isTagStateExpired(s, parsed.lastModified, isFetch))) {
620
792
  this.state.emit({ type: "cache.miss", meta: { reason: "tag-state-expired" } });
621
793
  return null;
@@ -638,6 +810,7 @@ var IncrementalRedisCacheHandler = class {
638
810
  const record = { lastModified: Date.now(), value: data };
639
811
  const serialized = serializeCacheRecord(record);
640
812
  const eKey = entryKey(this.state, cacheKey);
813
+ this.requestMemo.delete(eKey);
641
814
  const useRedis = !useLocal && gateRedis(this.state);
642
815
  if (useRedis) {
643
816
  try {
@@ -647,10 +820,19 @@ var IncrementalRedisCacheHandler = class {
647
820
  async () => {
648
821
  const client = await this.state.conn.getOrConnect();
649
822
  if (!client) {
650
- this.state.memEntries.set(eKey, serialized, ttl);
823
+ if (this.state.opts.fallback !== "never") {
824
+ this.state.memEntries.set(eKey, serialized, ttl);
825
+ }
651
826
  return;
652
827
  }
653
- await client.set(eKey, serialized, { EX: ttl });
828
+ await client.set(
829
+ eKey,
830
+ await compressValue(
831
+ serialized,
832
+ this.state.opts.rest.compression
833
+ ),
834
+ { EX: ttl }
835
+ );
654
836
  }
655
837
  );
656
838
  this.state.emit({ type: "cache.set", meta: { backend: "redis" } });
@@ -661,8 +843,15 @@ var IncrementalRedisCacheHandler = class {
661
843
  }
662
844
  }
663
845
  }
664
- this.state.memEntries.set(eKey, serialized, ttl);
665
- this.state.emit({ type: "cache.set", meta: { backend: "memory" } });
846
+ if (useLocal || this.state.opts.fallback !== "never") {
847
+ this.state.memEntries.set(eKey, serialized, ttl);
848
+ this.state.emit({ type: "cache.set", meta: { backend: "memory" } });
849
+ } else {
850
+ this.state.emit({
851
+ type: "cache.set.failed",
852
+ meta: { reason: "no-fallback" }
853
+ });
854
+ }
666
855
  } catch (err) {
667
856
  this.state.logger.error("set() error", {
668
857
  message: err.message
@@ -716,6 +905,7 @@ var IncrementalRedisCacheHandler = class {
716
905
  }
717
906
  }
718
907
  resetRequestCache() {
908
+ this.requestMemo.clear();
719
909
  }
720
910
  };
721
911
  function createIncrementalCacheHandler(opts) {